weekly release 4.0dev
[moodle.git] / Gruntfile.js
CommitLineData
adeb96d2
DW
1// This file is part of Moodle - http://moodle.org/
2//
3// Moodle is free software: you can redistribute it and/or modify
4// it under the terms of the GNU General Public License as published by
5// the Free Software Foundation, either version 3 of the License, or
6// (at your option) any later version.
7//
8// Moodle is distributed in the hope that it will be useful,
9// but WITHOUT ANY WARRANTY; without even the implied warranty of
10// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11// GNU General Public License for more details.
12//
13// You should have received a copy of the GNU General Public License
14// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
0b777a06 15/* jshint node: true, browser: false */
3adb62b7 16/* eslint-env node */
adeb96d2
DW
17
18/**
61fca0e0
AN
19 * Grunt configuration for Moodle.
20 *
adeb96d2
DW
21 * @copyright 2014 Andrew Nicols
22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23 */
24
25/**
61fca0e0 26 * Setup the Grunt Moodle environment.
a8109e75 27 *
61fca0e0
AN
28 * @param {Grunt} grunt
29 * @returns {Object}
adeb96d2 30 */
61fca0e0 31const setupMoodleEnvironment = grunt => {
a8109e75
AN
32 const fs = require('fs');
33 const path = require('path');
61fca0e0 34 const ComponentList = require(path.join(process.cwd(), '.grunt', 'components.js'));
adeb96d2 35
61fca0e0
AN
36 const getAmdConfiguration = () => {
37 // If the cwd is the amd directory in the current component then it will be empty.
38 // If the cwd is a child of the component's AMD directory, the relative directory will not start with ..
39 let inAMD = !path.relative(`${componentDirectory}/amd`, cwd).startsWith('..');
00cceb7f 40
61fca0e0
AN
41 // Globbing pattern for matching all AMD JS source files.
42 let amdSrc = [];
43 if (inComponent) {
44 amdSrc.push(
45 componentDirectory + "/amd/src/*.js",
46 componentDirectory + "/amd/src/**/*.js"
47 );
00cceb7f 48 } else {
61fca0e0 49 amdSrc = ComponentList.getAmdSrcGlobList();
00cceb7f 50 }
093be5c6 51
093be5c6 52 return {
61fca0e0
AN
53 inAMD,
54 amdSrc,
093be5c6
AN
55 };
56 };
57
61fca0e0
AN
58 const getYuiConfiguration = () => {
59 let yuiSrc = [];
60 if (inComponent) {
61 yuiSrc.push(componentDirectory + "/yui/src/**/*.js");
093be5c6 62 } else {
61fca0e0 63 yuiSrc = ComponentList.getYuiSrcGlobList(gruntFilePath + '/');
093be5c6
AN
64 }
65
61fca0e0
AN
66 return {
67 yuiSrc,
68 };
69 };
093be5c6 70
32638b3a
AN
71 const getStyleConfiguration = () => {
72 const ComponentList = require(path.join(process.cwd(), '.grunt', 'components.js'));
73 // Build the cssSrc and scssSrc.
74 // Valid paths are:
75 // [component]/styles.css; and either
76 // [theme/[themename]]/scss/**/*.scss; or
77 // [theme/[themename]]/style/*.css.
78 //
79 // If a theme has scss, then it is assumed that the style directory contains generated content.
80 let cssSrc = [];
81 let scssSrc = [];
82
83 const checkComponentDirectory = componentDirectory => {
84 const isTheme = componentDirectory.startsWith('theme/');
85 if (isTheme) {
86 const scssDirectory = `${componentDirectory}/scss`;
87
88 if (fs.existsSync(scssDirectory)) {
89 // This theme has an SCSS directory.
90 // Include all scss files within it recursively, but do not check for css files.
91 scssSrc.push(`${scssDirectory}/*.scss`);
92 scssSrc.push(`${scssDirectory}/**/*.scss`);
93 } else {
94 // This theme has no SCSS directory.
95 // Only hte CSS files in the top-level directory are checked.
96 cssSrc.push(`${componentDirectory}/style/*.css`);
97 }
98 } else {
99 // This is not a theme.
100 // All other plugin types are restricted to a single styles.css in their top level.
101 cssSrc.push(`${componentDirectory}/styles.css`);
102 }
103 };
104
105 if (inComponent) {
106 checkComponentDirectory(componentDirectory);
107 } else {
108 ComponentList.getComponentPaths(`${gruntFilePath}/`).forEach(componentPath => {
109 checkComponentDirectory(componentPath);
110 });
111 }
112
113 return {
114 cssSrc,
115 scssSrc,
116 };
117 };
118
61fca0e0
AN
119 /**
120 * Calculate the cwd, taking into consideration the `root` option (for Windows).
121 *
122 * @param {Object} grunt
123 * @returns {String} The current directory as best we can determine
124 */
125 const getCwd = grunt => {
61fca0e0
AN
126 let cwd = fs.realpathSync(process.env.PWD || process.cwd());
127
128 // Windows users can't run grunt in a subdirectory, so allow them to set
129 // the root by passing --root=path/to/dir.
130 if (grunt.option('root')) {
131 const root = grunt.option('root');
132 if (grunt.file.exists(__dirname, root)) {
133 cwd = fs.realpathSync(path.join(__dirname, root));
134 grunt.log.ok('Setting root to ' + cwd);
135 } else {
136 grunt.fail.fatal('Setting root to ' + root + ' failed - path does not exist');
137 }
093be5c6 138 }
a8109e75 139
61fca0e0
AN
140 return cwd;
141 };
a8109e75
AN
142
143 // Detect directories:
144 // * gruntFilePath The real path on disk to this Gruntfile.js
145 // * cwd The current working directory, which can be overridden by the `root` option
146 // * relativeCwd The cwd, relative to the Gruntfile.js
147 // * componentDirectory The root directory of the component if the cwd is in a valid component
148 // * inComponent Whether the cwd is in a valid component
149 // * runDir The componentDirectory or cwd if not in a component, relative to Gruntfile.js
150 // * fullRunDir The full path to the runDir
151 const gruntFilePath = fs.realpathSync(process.cwd());
152 const cwd = getCwd(grunt);
35e1470e 153 const relativeCwd = path.relative(gruntFilePath, cwd);
a8109e75
AN
154 const componentDirectory = ComponentList.getOwningComponentDirectory(relativeCwd);
155 const inComponent = !!componentDirectory;
61fca0e0 156 const inTheme = !!componentDirectory && componentDirectory.startsWith('theme/');
a8109e75
AN
157 const runDir = inComponent ? componentDirectory : relativeCwd;
158 const fullRunDir = fs.realpathSync(gruntFilePath + path.sep + runDir);
61fca0e0
AN
159 const {inAMD, amdSrc} = getAmdConfiguration();
160 const {yuiSrc} = getYuiConfiguration();
32638b3a 161 const {cssSrc, scssSrc} = getStyleConfiguration();
61fca0e0
AN
162
163 let files = null;
164 if (grunt.option('files')) {
165 // Accept a comma separated list of files to process.
166 files = grunt.option('files').split(',');
167 }
168
843cf97b
AN
169 grunt.log.debug('============================================================================');
170 grunt.log.debug(`= Node version: ${process.versions.node}`);
171 grunt.log.debug(`= grunt version: ${grunt.package.version}`);
172 grunt.log.debug(`= process.cwd: '` + process.cwd() + `'`);
173 grunt.log.debug(`= process.env.PWD: '${process.env.PWD}'`);
174 grunt.log.debug(`= path.sep '${path.sep}'`);
175 grunt.log.debug('============================================================================');
176 grunt.log.debug(`= gruntFilePath: '${gruntFilePath}'`);
177 grunt.log.debug(`= relativeCwd: '${relativeCwd}'`);
178 grunt.log.debug(`= componentDirectory: '${componentDirectory}'`);
179 grunt.log.debug(`= inComponent: '${inComponent}'`);
180 grunt.log.debug(`= runDir: '${runDir}'`);
181 grunt.log.debug(`= fullRunDir: '${fullRunDir}'`);
182 grunt.log.debug('============================================================================');
a8109e75
AN
183
184 if (inComponent) {
185 grunt.log.ok(`Running tasks for component directory ${componentDirectory}`);
186 }
187
61fca0e0
AN
188 return {
189 amdSrc,
190 componentDirectory,
191 cwd,
32638b3a 192 cssSrc,
61fca0e0
AN
193 files,
194 fullRunDir,
195 gruntFilePath,
196 inAMD,
197 inComponent,
198 inTheme,
199 relativeCwd,
200 runDir,
32638b3a 201 scssSrc,
61fca0e0 202 yuiSrc,
adeb96d2 203 };
61fca0e0 204};
adeb96d2 205
61fca0e0
AN
206/**
207 * Verify tha tthe current NodeJS version matches the required version in package.json.
208 *
209 * @param {Grunt} grunt
210 */
211const verifyNodeVersion = grunt => {
212 const semver = require('semver');
48b5817e 213
61fca0e0
AN
214 // Verify the node version is new enough.
215 var expected = semver.validRange(grunt.file.readJSON('package.json').engines.node);
216 var actual = semver.valid(process.version);
217 if (!semver.satisfies(actual, expected)) {
218 grunt.fail.fatal('Node version not satisfied. Require ' + expected + ', version installed: ' + actual);
219 }
220};
5b4debd2 221
61fca0e0
AN
222/**
223 * Grunt configuration.
224 *
225 * @param {Grunt} grunt
226 */
227module.exports = function(grunt) {
228 // Verify that the Node version meets our requirements.
229 verifyNodeVersion(grunt);
8b02e2d9 230
61fca0e0
AN
231 // Setup the Moodle environemnt within the Grunt object.
232 grunt.moodleEnv = setupMoodleEnvironment(grunt);
adeb96d2 233
38d4f754 234 /**
61fca0e0 235 * Add the named task.
38d4f754 236 *
61fca0e0
AN
237 * @param {string} name
238 * @param {Grunt} grunt
38d4f754 239 */
61fca0e0
AN
240 const addTask = (name, grunt) => {
241 const path = require('path');
242 const taskPath = path.resolve(`./.grunt/tasks/${name}.js`);
38d4f754 243
61fca0e0 244 grunt.log.debug(`Including tasks for ${name} from ${taskPath}`);
a84d5236 245
61fca0e0 246 require(path.resolve(`./.grunt/tasks/${name}.js`))(grunt);
38d4f754
RW
247 };
248
38d4f754 249
61fca0e0
AN
250 // Add Moodle task configuration.
251 addTask('gherkinlint', grunt);
252 addTask('ignorefiles', grunt);
65d070ae 253
61fca0e0
AN
254 addTask('javascript', grunt);
255 addTask('style', grunt);
adeb96d2 256
61fca0e0
AN
257 addTask('watch', grunt);
258 addTask('startup', grunt);
adeb96d2
DW
259
260 // Register the default task.
261 grunt.registerTask('default', ['startup']);
262};