Bug 26922: Regression tests
[koha.git] / gulpfile.js
blob1a37a15e3cd2cded1f277cf641cbeffd790a06ce
1 /* eslint-env node */
2 /* eslint no-console:"off" */
4 const { dest, parallel, series, src, watch } = require('gulp');
6 const child_process = require('child_process');
7 const fs = require('fs');
8 const os = require('os');
9 const path = require('path');
10 const util = require('util');
12 const sass = require("gulp-sass");
13 const cssnano = require("gulp-cssnano");
14 const rtlcss = require('gulp-rtlcss');
15 const sourcemaps = require('gulp-sourcemaps');
16 const autoprefixer = require('gulp-autoprefixer');
17 const concatPo = require('gulp-concat-po');
18 const exec = require('gulp-exec');
19 const merge = require('merge-stream');
20 const through2 = require('through2');
21 const Vinyl = require('vinyl');
22 const args = require('minimist')(process.argv.slice(2));
23 const rename = require('gulp-rename');
25 const STAFF_JS_BASE = "koha-tmpl/intranet-tmpl/prog/js";
26 const STAFF_CSS_BASE = "koha-tmpl/intranet-tmpl/prog/css";
27 const OPAC_JS_BASE = "koha-tmpl/opac-tmpl/bootstrap/js";
28 const OPAC_CSS_BASE = "koha-tmpl/opac-tmpl/bootstrap/css";
30 if (args.view == "opac") {
31     var css_base = OPAC_CSS_BASE;
32     var js_base = OPAC_JS_BASE;
33 } else {
34     var css_base = STAFF_CSS_BASE;
35     var js_base = STAFF_JS_BASE;
38 var sassOptions = {
39     errLogToConsole: true,
40     precision: 3
43 // CSS processing for development
44 function css() {
45     return src(css_base + "/src/**/*.scss")
46         .pipe(sourcemaps.init())
47         .pipe(sass(sassOptions).on('error', sass.logError))
48         .pipe(autoprefixer())
49         .pipe(sourcemaps.write('./maps'))
50         .pipe(dest(css_base))
52         .pipe(rtlcss())
53         .pipe(rename({
54             suffix: '-rtl'
55         })) // Append "-rtl" to the filename.
56         .pipe(dest(css_base));
59 // CSS processing for production
60 function build() {
61     return src(css_base + "/src/**/*.scss")
62         .pipe(sass(sassOptions).on('error', sass.logError))
63         .pipe(autoprefixer())
64         .pipe(cssnano({
65             zindex: false
66         }))
67         .pipe(dest(css_base))
69         .pipe(rtlcss())
70         .pipe(rename({
71             suffix: '-rtl'
72         })) // Append "-rtl" to the filename.
73         .pipe(dest(css_base));
76 const poTasks = {
77     'marc-MARC21': {
78         extract: po_extract_marc_marc21,
79         create: po_create_marc_marc21,
80         update: po_update_marc_marc21,
81     },
82     'marc-NORMARC': {
83         extract: po_extract_marc_normarc,
84         create: po_create_marc_normarc,
85         update: po_update_marc_normarc,
86     },
87     'marc-UNIMARC': {
88         extract: po_extract_marc_unimarc,
89         create: po_create_marc_unimarc,
90         update: po_update_marc_unimarc,
91     },
92     'staff-prog': {
93         extract: po_extract_staff,
94         create: po_create_staff,
95         update: po_update_staff,
96     },
97     'opac-bootstrap': {
98         extract: po_extract_opac,
99         create: po_create_opac,
100         update: po_update_opac,
101     },
102     'pref': {
103         extract: po_extract_pref,
104         create: po_create_pref,
105         update: po_update_pref,
106     },
107     'messages': {
108         extract: po_extract_messages,
109         create: po_create_messages,
110         update: po_update_messages,
111     },
112     'messages-js': {
113         extract: po_extract_messages_js,
114         create: po_create_messages_js,
115         update: po_update_messages_js,
116     },
117     'installer': {
118         extract: po_extract_installer,
119         create: po_create_installer,
120         update: po_update_installer,
121     },
122     'installer-MARC21': {
123         extract: po_extract_installer_marc21,
124         create: po_create_installer_marc21,
125         update: po_update_installer_marc21,
126     },
129 const poTypes = Object.keys(poTasks);
131 function po_extract_marc (type) {
132     return src(`koha-tmpl/*-tmpl/*/en/**/*${type}*`, { read: false, nocase: true })
133         .pipe(xgettext('misc/translator/xgettext.pl --charset=UTF-8 -s', `Koha-marc-${type}.pot`))
134         .pipe(dest('misc/translator'))
137 function po_extract_marc_marc21 ()  { return po_extract_marc('MARC21') }
138 function po_extract_marc_normarc () { return po_extract_marc('NORMARC') }
139 function po_extract_marc_unimarc () { return po_extract_marc('UNIMARC') }
141 function po_extract_staff () {
142     const globs = [
143         'koha-tmpl/intranet-tmpl/prog/en/**/*.tt',
144         'koha-tmpl/intranet-tmpl/prog/en/**/*.inc',
145         'koha-tmpl/intranet-tmpl/prog/en/xslt/*.xsl',
146         'koha-tmpl/intranet-tmpl/prog/en/columns.def',
147         '!koha-tmpl/intranet-tmpl/prog/en/**/*MARC21*',
148         '!koha-tmpl/intranet-tmpl/prog/en/**/*NORMARC*',
149         '!koha-tmpl/intranet-tmpl/prog/en/**/*UNIMARC*',
150         '!koha-tmpl/intranet-tmpl/prog/en/**/*marc21*',
151         '!koha-tmpl/intranet-tmpl/prog/en/**/*normarc*',
152         '!koha-tmpl/intranet-tmpl/prog/en/**/*unimarc*',
153     ];
155     return src(globs, { read: false, nocase: true })
156         .pipe(xgettext('misc/translator/xgettext.pl --charset=UTF-8 -s', 'Koha-staff-prog.pot'))
157         .pipe(dest('misc/translator'))
160 function po_extract_opac () {
161     const globs = [
162         'koha-tmpl/opac-tmpl/bootstrap/en/**/*.tt',
163         'koha-tmpl/opac-tmpl/bootstrap/en/**/*.inc',
164         'koha-tmpl/opac-tmpl/bootstrap/en/xslt/*.xsl',
165         '!koha-tmpl/opac-tmpl/bootstrap/en/**/*MARC21*',
166         '!koha-tmpl/opac-tmpl/bootstrap/en/**/*NORMARC*',
167         '!koha-tmpl/opac-tmpl/bootstrap/en/**/*UNIMARC*',
168         '!koha-tmpl/opac-tmpl/bootstrap/en/**/*marc21*',
169         '!koha-tmpl/opac-tmpl/bootstrap/en/**/*normarc*',
170         '!koha-tmpl/opac-tmpl/bootstrap/en/**/*unimarc*',
171     ];
173     return src(globs, { read: false, nocase: true })
174         .pipe(xgettext('misc/translator/xgettext.pl --charset=UTF-8 -s', 'Koha-opac-bootstrap.pot'))
175         .pipe(dest('misc/translator'))
178 const xgettext_options = '--from-code=UTF-8 --package-name Koha '
179     + '--package-version= -k -k__ -k__x -k__n:1,2 -k__nx:1,2 -k__xn:1,2 '
180     + '-k__p:1c,2 -k__px:1c,2 -k__np:1c,2,3 -k__npx:1c,2,3 -kN__ '
181     + '-kN__n:1,2 -kN__p:1c,2 -kN__np:1c,2,3 --force-po';
183 function po_extract_messages_js () {
184     const globs = [
185         'koha-tmpl/intranet-tmpl/prog/js/**/*.js',
186         'koha-tmpl/opac-tmpl/bootstrap/js/**/*.js',
187     ];
189     return src(globs, { read: false, nocase: true })
190         .pipe(xgettext(`xgettext -L JavaScript ${xgettext_options}`, 'Koha-messages-js.pot'))
191         .pipe(dest('misc/translator'))
194 function po_extract_messages () {
195     const perlStream = src(['**/*.pl', '**/*.pm'], { read: false, nocase: true })
196         .pipe(xgettext(`xgettext -L Perl ${xgettext_options}`, 'Koha-perl.pot'))
198     const ttStream = src([
199             'koha-tmpl/intranet-tmpl/prog/en/**/*.tt',
200             'koha-tmpl/intranet-tmpl/prog/en/**/*.inc',
201             'koha-tmpl/opac-tmpl/bootstrap/en/**/*.tt',
202             'koha-tmpl/opac-tmpl/bootstrap/en/**/*.inc',
203         ], { read: false, nocase: true })
204         .pipe(xgettext('misc/translator/xgettext-tt2 --from-code=UTF-8', 'Koha-tt.pot'))
206     const headers = {
207         'Project-Id-Version': 'Koha',
208         'Content-Type': 'text/plain; charset=UTF-8',
209     };
211     return merge(perlStream, ttStream)
212         .pipe(concatPo('Koha-messages.pot', { headers }))
213         .pipe(dest('misc/translator'))
216 function po_extract_pref () {
217     return src('koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/*.pref', { read: false })
218         .pipe(xgettext('misc/translator/xgettext-pref', 'Koha-pref.pot'))
219         .pipe(dest('misc/translator'))
222 function po_extract_installer () {
223     const globs = [
224         'installer/data/mysql/en/mandatory/*.yml',
225         'installer/data/mysql/en/optional/*.yml',
226     ];
228     return src(globs, { read: false, nocase: true })
229         .pipe(xgettext('misc/translator/xgettext-installer', 'Koha-installer.pot'))
230         .pipe(dest('misc/translator'))
233 function po_extract_installer_marc (type) {
234     const globs = `installer/data/mysql/en/marcflavour/${type}/**/*.yml`;
236     return src(globs, { read: false, nocase: true })
237         .pipe(xgettext('misc/translator/xgettext-installer', `Koha-installer-${type}.pot`))
238         .pipe(dest('misc/translator'))
241 function po_extract_installer_marc21 ()  { return po_extract_installer_marc('MARC21') }
243 function po_create_type (type) {
244     const access = util.promisify(fs.access);
245     const exec = util.promisify(child_process.exec);
247     const languages = getLanguages();
248     const promises = [];
249     for (const language of languages) {
250         const locale = language.split('-').filter(s => s.length !== 4).join('_');
251         const po = `misc/translator/po/${language}-${type}.po`;
252         const pot = `misc/translator/Koha-${type}.pot`;
254         const promise = access(po)
255             .catch(() => exec(`msginit -o ${po} -i ${pot} -l ${locale} --no-translator`))
256         promises.push(promise);
257     }
259     return Promise.all(promises);
262 function po_create_marc_marc21 ()       { return po_create_type('marc-MARC21') }
263 function po_create_marc_normarc ()      { return po_create_type('marc-NORMARC') }
264 function po_create_marc_unimarc ()      { return po_create_type('marc-UNIMARC') }
265 function po_create_staff ()             { return po_create_type('staff-prog') }
266 function po_create_opac ()              { return po_create_type('opac-bootstrap') }
267 function po_create_pref ()              { return po_create_type('pref') }
268 function po_create_messages ()          { return po_create_type('messages') }
269 function po_create_messages_js ()       { return po_create_type('messages-js') }
270 function po_create_installer ()         { return po_create_type('installer') }
271 function po_create_installer_marc21 ()  { return po_create_type('installer-MARC21') }
273 function po_update_type (type) {
274     const msgmerge_opts = '--backup=off --quiet --sort-output --update';
275     const cmd = `msgmerge ${msgmerge_opts} <%= file.path %> misc/translator/Koha-${type}.pot`;
276     const languages = getLanguages();
277     const globs = languages.map(language => `misc/translator/po/${language}-${type}.po`);
279     return src(globs)
280         .pipe(exec(cmd, { continueOnError: true }))
281         .pipe(exec.reporter({ err: false, stdout: false }))
284 function po_update_marc_marc21 ()       { return po_update_type('marc-MARC21') }
285 function po_update_marc_normarc ()      { return po_update_type('marc-NORMARC') }
286 function po_update_marc_unimarc ()      { return po_update_type('marc-UNIMARC') }
287 function po_update_staff ()             { return po_update_type('staff-prog') }
288 function po_update_opac ()              { return po_update_type('opac-bootstrap') }
289 function po_update_pref ()              { return po_update_type('pref') }
290 function po_update_messages ()          { return po_update_type('messages') }
291 function po_update_messages_js ()       { return po_update_type('messages-js') }
292 function po_update_installer ()         { return po_update_type('installer') }
293 function po_update_installer_marc21 ()  { return po_update_type('installer-MARC21') }
296  * Gulp plugin that executes xgettext-like command `cmd` on all files given as
297  * input, and then outputs the result as a POT file named `filename`.
298  * `cmd` should accept -o and -f options
299  */
300 function xgettext (cmd, filename) {
301     const filenames = [];
303     function transform (file, encoding, callback) {
304         filenames.push(path.relative(file.cwd, file.path));
305         callback();
306     }
308     function flush (callback) {
309         fs.mkdtemp(path.join(os.tmpdir(), 'koha-'), (err, folder) => {
310             const outputFilename = path.join(folder, filename);
311             const filesFilename = path.join(folder, 'files');
312             fs.writeFile(filesFilename, filenames.join(os.EOL), err => {
313                 if (err) return callback(err);
315                 const command = `${cmd} -o ${outputFilename} -f ${filesFilename}`;
316                 child_process.exec(command, err => {
317                     if (err) return callback(err);
319                     fs.readFile(outputFilename, (err, data) => {
320                         if (err) return callback(err);
322                         const file = new Vinyl();
323                         file.path = path.join(file.base, filename);
324                         file.contents = data;
325                         callback(null, file);
326                     });
327                 });
328             });
329         })
330     }
332     return through2.obj(transform, flush);
336  * Return languages selected for PO-related tasks
338  * This can be either languages given on command-line with --lang option, or
339  * all the languages found in misc/translator/po otherwise
340  */
341 function getLanguages () {
342     if (Array.isArray(args.lang)) {
343         return args.lang;
344     }
346     if (args.lang) {
347         return [args.lang];
348     }
350     const filenames = fs.readdirSync('misc/translator/po')
351         .filter(filename => filename.endsWith('.po'))
352         .filter(filename => !filename.startsWith('.'))
354     const re = new RegExp('-(' + poTypes.join('|') + ')\.po$');
355     languages = filenames.map(filename => filename.replace(re, ''))
357     return Array.from(new Set(languages));
360 exports.build = build;
361 exports.css = css;
363 exports['po:create'] = parallel(...poTypes.map(type => series(poTasks[type].extract, poTasks[type].create)));
364 exports['po:update'] = parallel(...poTypes.map(type => series(poTasks[type].extract, poTasks[type].update)));
365 exports['po:extract'] = parallel(...poTypes.map(type => poTasks[type].extract));
367 exports.default = function () {
368     watch(css_base + "/src/**/*.scss", series('css'));