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');
23 const packages = require('./package.json');
25 const logprefix = "[OpenEMR]".bold.cyan + " ";
29 all: [], // must always be empty
31 // Command Line Arguments
36 // Source file locations
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'
48 assets: 'public/assets/'
51 themes: 'public/themes',
52 misc_themes: 'public/themes/misc'
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");
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) {
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
79 // Clean up lingering static themes
80 function clean(done) {
81 del.sync([config.dest.themes + "/*"]);
85 // Parses command line arguments
86 function ingest(done) {
87 if (config.dev && typeof config.dev !== "boolean") {
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
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);
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);
116 .pipe(gulp.dest(config.dest.themes))
119 console.log(logprefix + "Finished compiling OpenEMR portal styles");
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);
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);
140 .pipe(gulp.dest(config.dest.themes))
143 console.log(logprefix + "Finished compiling OpenEMR base themes");
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);
158 .pipe(postcss([prefix()]))
159 .pipe(gap.prependText(autoGeneratedHeader))
160 .pipe(gulpif(!config.dev, csso()))
161 .pipe(gulpif(!config.dev, sourcemaps.write()))
165 .on('error', (err) => {
166 log_error(isSuccess, err);
168 .pipe(gulp.dest(config.dest.themes))
171 console.log(logprefix + "Finished compiling OpenEMR compact base themes");
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);
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);
193 .pipe(gulp.dest(config.dest.themes))
196 console.log(logprefix + "Finished compiling OpenEMR color themes");
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);
211 .pipe(postcss([prefix()]))
212 .pipe(gap.prependText(autoGeneratedHeader))
213 .pipe(gulpif(!config.dev, csso()))
214 .pipe(gulpif(!config.dev, sourcemaps.write()))
218 .on('error', (err) => {
219 log_error(isSuccess, err);
221 .pipe(gulp.dest(config.dest.themes))
224 console.log(logprefix + "Finished compiling OpenEMR compact color themes");
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);
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);
244 .pipe(gulp.dest(config.dest.themes))
247 console.log(logprefix + "Finished compiling OpenEMR tab navigation styles");
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);
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);
267 .pipe(gulp.dest(config.dest.misc_themes))
270 console.log(logprefix + "Finished compiling miscellaneous styles");
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);
286 .pipe(postcss([prefix()]))
287 .pipe(gap.prependText(autoGeneratedHeader))
288 .pipe(gulpif(!config.dev, csso()))
289 .pipe(gulpif(!config.dev, sourcemaps.write()))
293 .on('error', (err) => {
294 log_error(isSuccess, err);
296 .pipe(gulp.dest(config.dest.themes))
299 console.log(logprefix + "Finished compiling portal styles");
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);
315 .pipe(postcss([prefix()]))
316 .pipe(gap.prependText(autoGeneratedHeader))
317 .pipe(gulpif(!config.dev, csso()))
318 .pipe(gulpif(!config.dev, sourcemaps.write()))
322 .on('error', (err) => {
323 log_error(isSuccess, err);
325 .pipe(gulp.dest(config.dest.themes))
328 console.log(logprefix + "Finished compiling RTL base themes");
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);
345 .pipe(postcss([prefix()]))
346 .pipe(gap.prependText(autoGeneratedHeader))
347 .pipe(gulpif(!config.dev, csso()))
348 .pipe(gulpif(!config.dev, sourcemaps.write()))
350 prefix: "rtl_compact_"
352 .on('error', (err) => {
353 log_error(isSuccess, err);
355 .pipe(gulp.dest(config.dest.themes))
358 console.log(logprefix + "Finished compiling RTL base compact themes");
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);
374 .pipe(postcss([prefix()]))
375 .pipe(gap.prependText(autoGeneratedHeader))
376 .pipe(gulpif(!config.dev, csso()))
377 .pipe(gulpif(!config.dev, sourcemaps.write()))
381 .on('error', (err) => {
382 log_error(isSuccess, err);
384 .pipe(gulp.dest(config.dest.themes))
387 console.log(logprefix + "Compiled OpenEMR RTL color themes");
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);
404 .pipe(postcss([prefix()]))
405 .pipe(gap.prependText(autoGeneratedHeader))
406 .pipe(gulpif(!config.dev, csso()))
407 .pipe(gulpif(!config.dev, sourcemaps.write()))
409 prefix: "rtl_compact_"
411 .on('error', (err) => {
412 log_error(isSuccess, err);
414 .pipe(gulp.dest(config.dest.themes))
417 console.log(logprefix + "Finished compiling RTL compact color themes");
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);
431 .pipe(postcss([prefix()]))
432 .pipe(gap.prependText(autoGeneratedHeader))
433 .pipe(gulpif(!config.dev, csso()))
434 .pipe(gulpif(!config.dev, sourcemaps.write()))
438 .on('error', (err) => {
439 log_error(isSuccess, err);
441 .pipe(gulp.dest(config.dest.themes))
444 console.log(logprefix + "Finished compiling RTL tabs styles");
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);
458 .pipe(postcss([prefix()]))
459 .pipe(gap.prependText(autoGeneratedHeader))
460 .pipe(gulpif(!config.dev, csso()))
461 .pipe(gulpif(!config.dev, sourcemaps.write()))
465 .on('error', (err) => {
466 log_error(isSuccess, err);
468 .pipe(gulp.dest(config.dest.misc_themes))
471 console.log(logprefix + "Compiled rest of RTL SCSS");
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];
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)) {
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 // modify src/layout.scss in order for sass build to work by removing:
518 // @import "~bootstrap/scss/functions";
519 // @import "~bootstrap/scss/variables";
520 // @import "~bootstrap/scss/mixins";
521 gulp.src('node_modules/' + key + '/dist/**/*')
522 .pipe(gulp.dest(config.dist.assets + key + '/dist'));
523 gulp.src('node_modules/' + key + '/src/**/*')
524 .pipe(gulp.dest(config.dist.assets + key + '/src'))
525 .on('end', function() {
527 files: config.dist.assets + key + '/src/layout.scss',
529 /@import "~bootstrap\/scss\/functions";/,
530 /@import "~bootstrap\/scss\/variables";/,
531 /@import "~bootstrap\/scss\/mixins";/
536 } else if (key == "moment") {
537 gulp.src('node_modules/' + key + '/min/**/*')
538 .pipe(gulp.dest(config.dist.assets + key + '/min'));
539 gulp.src('node_modules/' + key + '/moment.js')
540 .pipe(gulp.dest(config.dist.assets + key));
541 } else if (fs.existsSync('node_modules/' + key + '/dist')) {
542 // only copy dist directory, if it exists
543 gulp.src('node_modules/' + key + '/dist/**/*')
544 .pipe(gulp.dest(config.dist.assets + key + '/dist'));
547 gulp.src('node_modules/' + key + '/**/*')
548 .pipe(gulp.dest(config.dist.assets + key));
553 console.log(logprefix + "Finished running OpenEMR gulp install task");
558 let isSuccess = true;
559 console.log(logprefix + "Running gulp watch task...");
560 // watch all changes and re-run styles
561 gulp.watch('./interface/**/*.scss', {
565 .on('error', (err) => {
566 log_error(isSuccess, err);
569 // watch php separately since autoprefix is not needed
570 gulp_watch('./interface/themes/*.php', {
573 .pipe(gulp.dest(config.dest.themes))
574 .on('error', (err) => {
575 log_error(isSuccess, err);
578 // watch all changes to css files in themes and
579 // autoprefix them before copying to public
580 return gulp_watch('./interface/themes/*.css', {
583 .pipe(postcss([prefix()]))
584 .on('error', (err) => {
585 log_error(isSuccess, err);
587 .pipe(gulp.dest(config.dest.themes))
590 console.log(logprefix + "Finished running gulp watch task");
596 let isSuccess = true;
597 console.log(logprefix + "Running gulp sync task...");
598 // copy all leftover root-level components to the theme directory
599 // hoping this is only temporary
600 // Copy php file separately since we don't need to autoprefix them
601 gulp.src(['interface/themes/*.php'])
602 .pipe(gulp.dest(config.dest.themes))
603 .on('error', (err) => {
604 log_error(isSuccess, err);
607 // Copy CSS files and autoprefix them
608 return gulp.src(['interface/themes/*.css'])
609 .pipe(postcss([prefix()]))
610 .on('error', (err) => {
611 log_error(isSuccess, err);
613 .pipe(gulp.dest(config.dest.themes))
616 console.log(logprefix + "Finished running gulp sync task");
622 exports.watch = watch;
624 // Export pertinent default task
625 // - Note that the default task runs if no other task is chosen,
626 // which is generally how this script is always used (except in
627 // rare case where the user is running the watch task).
628 if (config.install) {
629 exports.default = gulp.series(install);
631 exports.default = gulp.series(clean, ingest, styles, sync);