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_tabs: 'interface/themes/tabs_style_*.scss',
40 style_uni: 'interface/themes/oe-styles/style_*.scss',
41 style_color: 'interface/themes/colors/*.scss',
42 directional: 'interface/themes/directional.scss',
43 misc: 'interface/themes/misc/**/*.scss',
44 style_other: 'interface/themes/style*.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 function styles_style_other() {
101 let isSuccess = true;
102 return gulp.src(config.src.styles.style_other)
103 .pipe(sourcemaps.init())
104 .pipe(sass().on('error', (err) => {
105 log_error(isSuccess, err);
107 .pipe(postcss([prefix()]))
108 .pipe(gap.prependText(autoGeneratedHeader))
109 .pipe(gulpif(!config.dev, csso()))
110 .pipe(gulpif(!config.dev, sourcemaps.write()))
111 .on('error', (err) => {
112 log_error(isSuccess, err);
114 .pipe(gulp.dest(config.dest.themes))
117 console.log(logprefix + "Finished compiling OpenEMR root other styles");
122 // standard themes css compilation
123 function styles_style_uni() {
124 let isSuccess = true;
125 return gulp.src(config.src.styles.style_uni)
126 .pipe(gap.prependText('$compact-theme: false;\n'))
127 .pipe(injector.replace('// bs4import', '@import "../../../public/assets/bootstrap/scss/bootstrap";'))
128 .pipe(sourcemaps.init())
129 .pipe(sass().on('error', (err) => {
130 log_error(isSuccess, err);
132 .pipe(postcss([prefix()]))
133 .pipe(gap.prependText(autoGeneratedHeader))
134 .pipe(gulpif(!config.dev, csso()))
135 .pipe(gulpif(!config.dev, sourcemaps.write()))
136 .on('error', (err) => {
137 log_error(isSuccess, err);
139 .pipe(gulp.dest(config.dest.themes))
142 console.log(logprefix + "Finished compiling OpenEMR base themes");
147 // standard themes compact css compilation
148 function styles_style_uni_compact() {
149 let isSuccess = true;
150 return gulp.src(config.src.styles.style_uni)
151 .pipe(gap.prependText('@import "../compact-theme-defaults";\n'))
152 .pipe(injector.replace('// bs4import', '@import "../oemr_compact_imports";'))
153 .pipe(sourcemaps.init())
154 .pipe(sass().on('error', (err) => {
155 log_error(isSuccess, err);
157 .pipe(postcss([prefix()]))
158 .pipe(gap.prependText(autoGeneratedHeader))
159 .pipe(gulpif(!config.dev, csso()))
160 .pipe(gulpif(!config.dev, sourcemaps.write()))
164 .on('error', (err) => {
165 log_error(isSuccess, err);
167 .pipe(gulp.dest(config.dest.themes))
170 console.log(logprefix + "Finished compiling OpenEMR compact base themes");
175 // color themes css compilation
176 function styles_style_color() {
177 let isSuccess = true;
178 return gulp.src(config.src.styles.style_color)
179 .pipe(gap.prependText('$compact-theme: false;\n'))
180 .pipe(injector.replace('// bs4import', '@import "../../../public/assets/bootstrap/scss/bootstrap";'))
181 .pipe(sourcemaps.init())
182 .pipe(sass().on('error', (err) => {
183 log_error(isSuccess, err);
185 .pipe(postcss([prefix()]))
186 .pipe(gap.prependText(autoGeneratedHeader))
187 .pipe(gulpif(!config.dev, csso()))
188 .pipe(gulpif(!config.dev, sourcemaps.write()))
189 .on('error', (err) => {
190 log_error(isSuccess, err);
192 .pipe(gulp.dest(config.dest.themes))
195 console.log(logprefix + "Finished compiling OpenEMR color themes");
200 // color themes compact css compilation
201 function styles_style_color_compact() {
202 let isSuccess = true;
203 return gulp.src(config.src.styles.style_color)
204 .pipe(gap.prependText('@import "../compact-theme-defaults";\n'))
205 .pipe(injector.replace('// bs4import', '@import "../oemr_compact_imports";'))
206 .pipe(sourcemaps.init())
207 .pipe(sass().on('error', (err) => {
208 log_error(isSuccess, err);
210 .pipe(postcss([prefix()]))
211 .pipe(gap.prependText(autoGeneratedHeader))
212 .pipe(gulpif(!config.dev, csso()))
213 .pipe(gulpif(!config.dev, sourcemaps.write()))
217 .on('error', (err) => {
218 log_error(isSuccess, err);
220 .pipe(gulp.dest(config.dest.themes))
223 console.log(logprefix + "Finished compiling OpenEMR compact color themes");
228 // Tabs CSS compilation
229 function styles_style_tabs() {
230 let isSuccess = true;
231 return gulp.src(config.src.styles.style_tabs)
232 .pipe(sourcemaps.init())
233 .pipe(sass().on('error', (err) => {
234 log_error(isSuccess, err);
236 .pipe(postcss([prefix()]))
237 .pipe(gap.prependText(autoGeneratedHeader))
238 .pipe(gulpif(!config.dev, csso()))
239 .pipe(gulpif(!config.dev, sourcemaps.write()))
240 .on('error', (err) => {
241 log_error(isSuccess, err);
243 .pipe(gulp.dest(config.dest.themes))
246 console.log(logprefix + "Finished compiling OpenEMR tab navigation styles");
251 // For anything else that needs to be moved, use misc themes
252 function styles_style_misc() {
253 let isSuccess = true;
254 return gulp.src(config.src.styles.misc)
255 .pipe(sourcemaps.init())
256 .pipe(sass().on('error', (err) => {
257 log_error(isSuccess, err);
259 .pipe(postcss([prefix()]))
260 .pipe(gap.prependText(autoGeneratedHeader))
261 .pipe(gulpif(!config.dev, csso()))
262 .pipe(gulpif(!config.dev, sourcemaps.write()))
263 .on('error', (err) => {
264 log_error(isSuccess, err);
266 .pipe(gulp.dest(config.dest.misc_themes))
269 console.log(logprefix + "Finished compiling miscellaneous styles");
274 // rtl standard themes css compilation
275 function rtl_style_uni() {
276 let isSuccess = true;
277 return gulp.src(config.src.styles.style_uni)
278 .pipe(gap.prependText('$compact-theme: false;\n$dir: rtl;\n@import "../rtl";\n')) // watch out for this relative path!
279 .pipe(gap.appendText('@include if-rtl { @include rtl_style; #bigCal { border-right: 1px solid $black !important; } }\n'))
280 .pipe(injector.replace('// bs4import', '@import "../oemr-rtl";'))
281 .pipe(sourcemaps.init())
282 .pipe(sass().on('error', (err) => {
283 log_error(isSuccess, err);
285 .pipe(postcss([prefix()]))
286 .pipe(gap.prependText(autoGeneratedHeader))
287 .pipe(gulpif(!config.dev, csso()))
288 .pipe(gulpif(!config.dev, sourcemaps.write()))
292 .on('error', (err) => {
293 log_error(isSuccess, err);
295 .pipe(gulp.dest(config.dest.themes))
298 console.log(logprefix + "Finished compiling RTL base themes");
303 // rtl standard themes compact css compilation
304 function rtl_style_uni_compact() {
305 let isSuccess = true;
306 return gulp.src(config.src.styles.style_uni)
307 .pipe(gap.prependText('@import "../compact-theme-defaults";\n'))
308 .pipe(gap.prependText('$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_compact_imports";'))
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()))
320 prefix: "rtl_compact_"
322 .on('error', (err) => {
323 log_error(isSuccess, err);
325 .pipe(gulp.dest(config.dest.themes))
328 console.log(logprefix + "Finished compiling RTL base compact themes");
333 // rtl color themes css compilation
334 function rtl_style_color() {
335 let isSuccess = true;
336 return gulp.src(config.src.styles.style_color)
337 .pipe(gap.prependText('$compact-theme: false;\n$dir: rtl;\n@import "../rtl";\n')) // watch out for this relative path!
338 .pipe(gap.appendText('@include if-rtl { @include rtl_style; #bigCal { border-right: 1px solid $black !important; } }\n'))
339 .pipe(injector.replace('// bs4import', '@import "../oemr-rtl";'))
340 .pipe(sourcemaps.init())
341 .pipe(sass().on('error', (err) => {
342 log_error(isSuccess, err);
344 .pipe(postcss([prefix()]))
345 .pipe(gap.prependText(autoGeneratedHeader))
346 .pipe(gulpif(!config.dev, csso()))
347 .pipe(gulpif(!config.dev, sourcemaps.write()))
351 .on('error', (err) => {
352 log_error(isSuccess, err);
354 .pipe(gulp.dest(config.dest.themes))
357 console.log(logprefix + "Compiled OpenEMR RTL color themes");
362 // rtl color themes compact css compilation
363 function rtl_style_color_compact() {
364 let isSuccess = true;
365 return gulp.src(config.src.styles.style_color)
366 .pipe(gap.prependText('@import "../compact-theme-defaults";\n'))
367 .pipe(gap.prependText('$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_compact_imports";'))
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()))
379 prefix: "rtl_compact_"
381 .on('error', (err) => {
382 log_error(isSuccess, err);
384 .pipe(gulp.dest(config.dest.themes))
387 console.log(logprefix + "Finished compiling RTL compact color themes");
392 // rtl standard themes css compilation
393 function rtl_style_tabs() {
394 let isSuccess = true;
395 return gulp.src(config.src.styles.style_tabs)
396 .pipe(gap.prependText('$dir: rtl;\n@import "rtl";\n')) // watch out for this relative path!
397 .pipe(sourcemaps.init())
398 .pipe(sass().on('error', (err) => {
399 log_error(isSuccess, err);
401 .pipe(postcss([prefix()]))
402 .pipe(gap.prependText(autoGeneratedHeader))
403 .pipe(gulpif(!config.dev, csso()))
404 .pipe(gulpif(!config.dev, sourcemaps.write()))
408 .on('error', (err) => {
409 log_error(isSuccess, err);
411 .pipe(gulp.dest(config.dest.themes))
414 console.log(logprefix + "Finished compiling RTL tabs styles");
419 // For anything else that needs to be moved, use misc themes
420 function rtl_style_misc() {
421 let isSuccess = true;
422 return gulp.src(config.src.styles.misc)
423 .pipe(gap.prependText('$dir: rtl;\n')) // Simply a flag here due to a hierarchy possibly being created
424 .pipe(sourcemaps.init())
425 .pipe(sass().on('error', (err) => {
426 log_error(isSuccess, err);
428 .pipe(postcss([prefix()]))
429 .pipe(gap.prependText(autoGeneratedHeader))
430 .pipe(gulpif(!config.dev, csso()))
431 .pipe(gulpif(!config.dev, sourcemaps.write()))
435 .on('error', (err) => {
436 log_error(isSuccess, err);
438 .pipe(gulp.dest(config.dest.misc_themes))
441 console.log(logprefix + "Compiled rest of RTL SCSS");
447 const styles = gulp.parallel(styles_style_color, styles_style_color_compact, styles_style_uni, styles_style_uni_compact, styles_style_tabs, styles_style_misc, styles_style_other, rtl_style_color, rtl_style_color_compact, rtl_style_uni, rtl_style_uni_compact, rtl_style_tabs, rtl_style_misc);
449 // Copies (and distills, if possible) assets from node_modules to public/assets
450 function install(done) {
451 console.log(logprefix + "Running OpenEMR gulp install task...");
452 // combine dependencies and napa sources into one object
453 const dependencies = packages.dependencies;
454 for (let key in packages.napa) {
455 if (Object.prototype.hasOwnProperty.call(packages.napa, key)) {
456 dependencies[key] = packages.napa[key];
460 for (let key in dependencies) {
461 // check if the property/key is defined in the object itself, not in parent
462 if (Object.prototype.hasOwnProperty.call(dependencies, key)) {
464 // dwv is special and need to copy dist, decoders and locales
465 gulp.src("node_modules/" + key + "/dist/**/*").pipe(
466 gulp.dest(config.dist.assets + key + "/dist")
468 gulp.src("node_modules/" + key + "/decoders/**/*").pipe(
469 gulp.dest(config.dist.assets + key + "/decoders")
471 gulp.src("node_modules/" + key + "/locales/**/*").pipe(
472 gulp.dest(config.dist.assets + key + "/locales")
474 } else if (key == "bootstrap" || key == "bootstrap-rtl") {
475 // bootstrap and bootstrap-v4-rtl are special and need to copy dist and scss
476 gulp.src("node_modules/" + key + "/dist/**/*").pipe(
477 gulp.dest(config.dist.assets + key + "/dist")
479 gulp.src("node_modules/" + key + "/scss/**/*").pipe(
480 gulp.dest(config.dist.assets + key + "/scss")
482 } else if (key == "@fortawesome/fontawesome-free") {
483 // @fortawesome/fontawesome-free is special and need to copy css, scss, and webfonts
484 gulp.src("node_modules/" + key + "/css/**/*").pipe(
485 gulp.dest(config.dist.assets + key + "/css")
487 gulp.src("node_modules/" + key + "/scss/**/*").pipe(
488 gulp.dest(config.dist.assets + key + "/scss")
490 gulp.src("node_modules/" + key + "/webfonts/**/*").pipe(
491 gulp.dest(config.dist.assets + key + "/webfonts")
493 } else if (key == "moment") {
494 gulp.src("node_modules/" + key + "/min/**/*").pipe(
495 gulp.dest(config.dist.assets + key + "/min")
497 gulp.src("node_modules/" + key + "/moment.js").pipe(
498 gulp.dest(config.dist.assets + key)
500 } else if (fs.existsSync("node_modules/" + key + "/dist")) {
501 // only copy dist directory, if it exists
502 gulp.src("node_modules/" + key + "/dist/**/*").pipe(
503 gulp.dest(config.dist.assets + key + "/dist")
507 gulp.src("node_modules/" + key + "/**/*").pipe(
508 gulp.dest(config.dist.assets + key)
514 console.log(logprefix + "Finished running OpenEMR gulp install task");
519 let isSuccess = true;
520 console.log(logprefix + "Running gulp watch task...");
521 // watch all changes and re-run styles
522 gulp.watch('./interface/**/*.scss', {
526 .on('error', (err) => {
527 log_error(isSuccess, err);
530 // watch php separately since autoprefix is not needed
531 gulp_watch('./interface/themes/*.php', {
534 .pipe(gulp.dest(config.dest.themes))
535 .on('error', (err) => {
536 log_error(isSuccess, err);
539 // watch all changes to css files in themes and
540 // autoprefix them before copying to public
541 return gulp_watch('./interface/themes/*.css', {
544 .pipe(postcss([prefix()]))
545 .on('error', (err) => {
546 log_error(isSuccess, err);
548 .pipe(gulp.dest(config.dest.themes))
551 console.log(logprefix + "Finished running gulp watch task");
557 let isSuccess = true;
558 console.log(logprefix + "Running gulp sync task...");
559 // copy all leftover root-level components to the theme directory
560 // hoping this is only temporary
561 // Copy php file separately since we don't need to autoprefix them
562 gulp.src(['interface/themes/*.php'])
563 .pipe(gulp.dest(config.dest.themes))
564 .on('error', (err) => {
565 log_error(isSuccess, err);
568 // Copy CSS files and autoprefix them
569 return gulp.src(['interface/themes/*.css'])
570 .pipe(postcss([prefix()]))
571 .on('error', (err) => {
572 log_error(isSuccess, err);
574 .pipe(gulp.dest(config.dest.themes))
577 console.log(logprefix + "Finished running gulp sync task");
583 exports.watch = watch;
585 // Export pertinent default task
586 // - Note that the default task runs if no other task is chosen,
587 // which is generally how this script is always used (except in
588 // rare case where the user is running the watch task).
589 if (config.install) {
590 exports.default = gulp.series(install);
592 exports.default = gulp.series(clean, ingest, styles, sync);