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'
47 assets: 'public/assets/'
50 themes: 'public/themes',
51 misc_themes: 'public/themes/misc'
56 console.log("\nCopying OpenEMR dependencies using Gulp".bold.yellow + "\n");
57 } else if (config.build) {
58 console.log("\nBuilding OpenEMR themes using Gulp".bold.yellow + "\n");
59 } else if (config.dev) {
60 console.log("\nBuilding OpenEMR themes using Dev Flag for Gulp".bold.yellow + "\n");
61 } else if (config.all) {
62 console.log("\nBuilding OpenEMR themes using All Flag for Gulp".bold.yellow + "\n");
64 // This is used for gulp watch & other misc things
65 console.log("\nRunning Gulp for OpenEMR".bold.yellow + "\n");
68 function log_error(isSuccess, err) {
70 console.error(logprefix + "An error occured! Check the log for details.");
71 // Log error to console
72 console.error(err.toString().red);
73 // Kills gulp on error since if we keep running it will
78 // Clean up lingering static themes
79 function clean(done) {
80 del.sync([config.dest.themes + "/*"]);
84 // Parses command line arguments
85 function ingest(done) {
86 if (config.dev && typeof config.dev !== "boolean") {
92 // definition of header for all compiled css
93 const autoGeneratedHeader = `
94 /*! This style sheet was autogenerated using gulp + scss
95 * For usage instructions, see: https://github.com/openemr/openemr/blob/master/interface/README.md
99 // standard themes css compilation
100 function styles_style_uni() {
101 let isSuccess = true;
102 return gulp.src(config.src.styles.style_uni)
103 .pipe(gap.prependText('$compact-theme: false;\n'))
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 base themes");
124 // standard themes compact css compilation
125 function styles_style_uni_compact() {
126 let isSuccess = true;
127 return gulp.src(config.src.styles.style_uni)
128 .pipe(gap.prependText('@import "../compact-theme-defaults";\n'))
129 .pipe(injector.replace('// bs4import', '@import "../oemr_compact_imports";'))
130 .pipe(sourcemaps.init())
131 .pipe(sass().on('error', (err) => {
132 log_error(isSuccess, err);
134 .pipe(postcss([prefix()]))
135 .pipe(gap.prependText(autoGeneratedHeader))
136 .pipe(gulpif(!config.dev, csso()))
137 .pipe(gulpif(!config.dev, sourcemaps.write()))
141 .on('error', (err) => {
142 log_error(isSuccess, err);
144 .pipe(gulp.dest(config.dest.themes))
147 console.log(logprefix + "Finished compiling OpenEMR compact base themes");
152 // color themes css compilation
153 function styles_style_color() {
154 let isSuccess = true;
155 return gulp.src(config.src.styles.style_color)
156 .pipe(gap.prependText('$compact-theme: false;\n'))
157 .pipe(injector.replace('// bs4import', '@import "../../../public/assets/bootstrap/scss/bootstrap";'))
158 .pipe(sourcemaps.init())
159 .pipe(sass().on('error', (err) => {
160 log_error(isSuccess, err);
162 .pipe(postcss([prefix()]))
163 .pipe(gap.prependText(autoGeneratedHeader))
164 .pipe(gulpif(!config.dev, csso()))
165 .pipe(gulpif(!config.dev, sourcemaps.write()))
166 .on('error', (err) => {
167 log_error(isSuccess, err);
169 .pipe(gulp.dest(config.dest.themes))
172 console.log(logprefix + "Finished compiling OpenEMR color themes");
177 // color themes compact css compilation
178 function styles_style_color_compact() {
179 let isSuccess = true;
180 return gulp.src(config.src.styles.style_color)
181 .pipe(gap.prependText('@import "../compact-theme-defaults";\n'))
182 .pipe(injector.replace('// bs4import', '@import "../oemr_compact_imports";'))
183 .pipe(sourcemaps.init())
184 .pipe(sass().on('error', (err) => {
185 log_error(isSuccess, err);
187 .pipe(postcss([prefix()]))
188 .pipe(gap.prependText(autoGeneratedHeader))
189 .pipe(gulpif(!config.dev, csso()))
190 .pipe(gulpif(!config.dev, sourcemaps.write()))
194 .on('error', (err) => {
195 log_error(isSuccess, err);
197 .pipe(gulp.dest(config.dest.themes))
200 console.log(logprefix + "Finished compiling OpenEMR compact color themes");
205 // Tabs CSS compilation
206 function styles_style_tabs() {
207 let isSuccess = true;
208 return gulp.src(config.src.styles.style_tabs)
209 .pipe(sourcemaps.init())
210 .pipe(sass().on('error', (err) => {
211 log_error(isSuccess, err);
213 .pipe(postcss([prefix()]))
214 .pipe(gap.prependText(autoGeneratedHeader))
215 .pipe(gulpif(!config.dev, csso()))
216 .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 tab navigation styles");
228 // For anything else that needs to be moved, use misc themes
229 function styles_style_misc() {
230 let isSuccess = true;
231 return gulp.src(config.src.styles.misc)
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.misc_themes))
246 console.log(logprefix + "Finished compiling miscellaneous styles");
251 // rtl standard themes css compilation
252 function rtl_style_uni() {
253 let isSuccess = true;
254 return gulp.src(config.src.styles.style_uni)
255 .pipe(gap.prependText('$compact-theme: false;\n$dir: rtl;\n@import "../rtl";\n')) // watch out for this relative path!
256 .pipe(gap.appendText('@include if-rtl { @include rtl_style; #bigCal { border-right: 1px solid $black !important; } }\n'))
257 .pipe(injector.replace('// bs4import', '@import "../oemr-rtl";'))
258 .pipe(sourcemaps.init())
259 .pipe(sass().on('error', (err) => {
260 log_error(isSuccess, err);
262 .pipe(postcss([prefix()]))
263 .pipe(gap.prependText(autoGeneratedHeader))
264 .pipe(gulpif(!config.dev, csso()))
265 .pipe(gulpif(!config.dev, sourcemaps.write()))
269 .on('error', (err) => {
270 log_error(isSuccess, err);
272 .pipe(gulp.dest(config.dest.themes))
275 console.log(logprefix + "Finished compiling RTL base themes");
280 // rtl standard themes compact css compilation
281 function rtl_style_uni_compact() {
282 let isSuccess = true;
283 return gulp.src(config.src.styles.style_uni)
284 .pipe(gap.prependText('@import "../compact-theme-defaults";\n'))
285 .pipe(gap.prependText('$dir: rtl;\n@import "../rtl";\n')) // watch out for this relative path!
286 .pipe(gap.appendText('@include if-rtl { @include rtl_style; #bigCal { border-right: 1px solid $black !important; } }\n'))
287 .pipe(injector.replace('// bs4import', '@import "../oemr_rtl_compact_imports";'))
288 .pipe(sourcemaps.init())
289 .pipe(sass().on('error', (err) => {
290 log_error(isSuccess, err);
292 .pipe(postcss([prefix()]))
293 .pipe(gap.prependText(autoGeneratedHeader))
294 .pipe(gulpif(!config.dev, csso()))
295 .pipe(gulpif(!config.dev, sourcemaps.write()))
297 prefix: "rtl_compact_"
299 .on('error', (err) => {
300 log_error(isSuccess, err);
302 .pipe(gulp.dest(config.dest.themes))
305 console.log(logprefix + "Finished compiling RTL base compact themes");
310 // rtl color themes css compilation
311 function rtl_style_color() {
312 let isSuccess = true;
313 return gulp.src(config.src.styles.style_color)
314 .pipe(gap.prependText('$compact-theme: false;\n$dir: rtl;\n@import "../rtl";\n')) // watch out for this relative path!
315 .pipe(gap.appendText('@include if-rtl { @include rtl_style; #bigCal { border-right: 1px solid $black !important; } }\n'))
316 .pipe(injector.replace('// bs4import', '@import "../oemr-rtl";'))
317 .pipe(sourcemaps.init())
318 .pipe(sass().on('error', (err) => {
319 log_error(isSuccess, err);
321 .pipe(postcss([prefix()]))
322 .pipe(gap.prependText(autoGeneratedHeader))
323 .pipe(gulpif(!config.dev, csso()))
324 .pipe(gulpif(!config.dev, sourcemaps.write()))
328 .on('error', (err) => {
329 log_error(isSuccess, err);
331 .pipe(gulp.dest(config.dest.themes))
334 console.log(logprefix + "Compiled OpenEMR RTL color themes");
339 // rtl color themes compact css compilation
340 function rtl_style_color_compact() {
341 let isSuccess = true;
342 return gulp.src(config.src.styles.style_color)
343 .pipe(gap.prependText('@import "../compact-theme-defaults";\n'))
344 .pipe(gap.prependText('$dir: rtl;\n@import "../rtl";\n')) // watch out for this relative path!
345 .pipe(gap.appendText('@include if-rtl { @include rtl_style; #bigCal { border-right: 1px solid $black !important; } }\n'))
346 .pipe(injector.replace('// bs4import', '@import "../oemr_rtl_compact_imports";'))
347 .pipe(sourcemaps.init())
348 .pipe(sass().on('error', (err) => {
349 log_error(isSuccess, err);
351 .pipe(postcss([prefix()]))
352 .pipe(gap.prependText(autoGeneratedHeader))
353 .pipe(gulpif(!config.dev, csso()))
354 .pipe(gulpif(!config.dev, sourcemaps.write()))
356 prefix: "rtl_compact_"
358 .on('error', (err) => {
359 log_error(isSuccess, err);
361 .pipe(gulp.dest(config.dest.themes))
364 console.log(logprefix + "Finished compiling RTL compact color themes");
369 // rtl standard themes css compilation
370 function rtl_style_tabs() {
371 let isSuccess = true;
372 return gulp.src(config.src.styles.style_tabs)
373 .pipe(gap.prependText('$dir: rtl;\n@import "rtl";\n')) // watch out for this relative path!
374 .pipe(sourcemaps.init())
375 .pipe(sass().on('error', (err) => {
376 log_error(isSuccess, err);
378 .pipe(postcss([prefix()]))
379 .pipe(gap.prependText(autoGeneratedHeader))
380 .pipe(gulpif(!config.dev, csso()))
381 .pipe(gulpif(!config.dev, sourcemaps.write()))
385 .on('error', (err) => {
386 log_error(isSuccess, err);
388 .pipe(gulp.dest(config.dest.themes))
391 console.log(logprefix + "Finished compiling RTL tabs styles");
396 // For anything else that needs to be moved, use misc themes
397 function rtl_style_misc() {
398 let isSuccess = true;
399 return gulp.src(config.src.styles.misc)
400 .pipe(gap.prependText('$dir: rtl;\n')) // Simply a flag here due to a hierarchy possibly being created
401 .pipe(sourcemaps.init())
402 .pipe(sass().on('error', (err) => {
403 log_error(isSuccess, err);
405 .pipe(postcss([prefix()]))
406 .pipe(gap.prependText(autoGeneratedHeader))
407 .pipe(gulpif(!config.dev, csso()))
408 .pipe(gulpif(!config.dev, sourcemaps.write()))
412 .on('error', (err) => {
413 log_error(isSuccess, err);
415 .pipe(gulp.dest(config.dest.misc_themes))
418 console.log(logprefix + "Compiled rest of RTL SCSS");
424 const styles = gulp.parallel(styles_style_color, styles_style_color_compact, styles_style_uni, styles_style_uni_compact, styles_style_tabs, styles_style_misc, rtl_style_color, rtl_style_color_compact, rtl_style_uni, rtl_style_uni_compact, rtl_style_tabs, rtl_style_misc);
426 // Copies (and distills, if possible) assets from node_modules to public/assets
427 function install(done) {
428 console.log(logprefix + "Running OpenEMR gulp install task...");
429 // combine dependencies and napa sources into one object
430 const dependencies = packages.dependencies;
431 for (let key in packages.napa) {
432 if (Object.prototype.hasOwnProperty.call(packages.napa, key)) {
433 dependencies[key] = packages.napa[key];
437 for (let key in dependencies) {
438 // check if the property/key is defined in the object itself, not in parent
439 if (Object.prototype.hasOwnProperty.call(dependencies, key)) {
441 // dwv is special and need to copy dist, decoders and locales
442 gulp.src("node_modules/" + key + "/dist/**/*").pipe(
443 gulp.dest(config.dist.assets + key + "/dist")
445 gulp.src("node_modules/" + key + "/decoders/**/*").pipe(
446 gulp.dest(config.dist.assets + key + "/decoders")
448 gulp.src("node_modules/" + key + "/locales/**/*").pipe(
449 gulp.dest(config.dist.assets + key + "/locales")
451 } else if (key == "bootstrap" || key == "bootstrap-rtl") {
452 // bootstrap and bootstrap-v4-rtl are special and need to copy dist and scss
453 gulp.src("node_modules/" + key + "/dist/**/*").pipe(
454 gulp.dest(config.dist.assets + key + "/dist")
456 gulp.src("node_modules/" + key + "/scss/**/*").pipe(
457 gulp.dest(config.dist.assets + key + "/scss")
459 } else if (key == "@fortawesome/fontawesome-free") {
460 // @fortawesome/fontawesome-free is special and need to copy css, scss, and webfonts
461 gulp.src("node_modules/" + key + "/css/**/*").pipe(
462 gulp.dest(config.dist.assets + key + "/css")
464 gulp.src("node_modules/" + key + "/scss/**/*").pipe(
465 gulp.dest(config.dist.assets + key + "/scss")
467 gulp.src("node_modules/" + key + "/webfonts/**/*").pipe(
468 gulp.dest(config.dist.assets + key + "/webfonts")
470 } else if (key == "moment") {
471 gulp.src("node_modules/" + key + "/min/**/*").pipe(
472 gulp.dest(config.dist.assets + key + "/min")
474 gulp.src("node_modules/" + key + "/moment.js").pipe(
475 gulp.dest(config.dist.assets + key)
477 } else if (fs.existsSync("node_modules/" + key + "/dist")) {
478 // only copy dist directory, if it exists
479 gulp.src("node_modules/" + key + "/dist/**/*").pipe(
480 gulp.dest(config.dist.assets + key + "/dist")
484 gulp.src("node_modules/" + key + "/**/*").pipe(
485 gulp.dest(config.dist.assets + key)
491 console.log(logprefix + "Finished running OpenEMR gulp install task");
496 let isSuccess = true;
497 console.log(logprefix + "Running gulp watch task...");
498 // watch all changes and re-run styles
499 gulp.watch('./interface/**/*.scss', {
503 .on('error', (err) => {
504 log_error(isSuccess, err);
507 // watch php separately since autoprefix is not needed
508 gulp_watch('./interface/themes/*.php', {
511 .pipe(gulp.dest(config.dest.themes))
512 .on('error', (err) => {
513 log_error(isSuccess, err);
516 // watch all changes to css files in themes and
517 // autoprefix them before copying to public
518 return gulp_watch('./interface/themes/*.css', {
521 .pipe(postcss([prefix()]))
522 .on('error', (err) => {
523 log_error(isSuccess, err);
525 .pipe(gulp.dest(config.dest.themes))
528 console.log(logprefix + "Finished running gulp watch task");
534 let isSuccess = true;
535 console.log(logprefix + "Running gulp sync task...");
536 // copy all leftover root-level components to the theme directory
537 // hoping this is only temporary
538 // Copy php file separately since we don't need to autoprefix them
539 gulp.src(['interface/themes/*.php'])
540 .pipe(gulp.dest(config.dest.themes))
541 .on('error', (err) => {
542 log_error(isSuccess, err);
545 // Copy CSS files and autoprefix them
546 return gulp.src(['interface/themes/*.css'])
547 .pipe(postcss([prefix()]))
548 .on('error', (err) => {
549 log_error(isSuccess, err);
551 .pipe(gulp.dest(config.dest.themes))
554 console.log(logprefix + "Finished running gulp sync task");
560 exports.watch = watch;
562 // Export pertinent default task
563 // - Note that the default task runs if no other task is chosen,
564 // which is generally how this script is always used (except in
565 // rare case where the user is running the watch task).
566 if (config.install) {
567 exports.default = gulp.series(install);
569 exports.default = gulp.series(clean, ingest, styles, sync);