Commit | Line | Data |
---|---|---|
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 | 31 | const 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 | */ | |
211 | const 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 | */ | |
227 | module.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 | ||
2f2ecdae AN |
249 | // Startup tasks. |
250 | grunt.moodleEnv.startupTasks = []; | |
38d4f754 | 251 | |
61fca0e0 AN |
252 | // Add Moodle task configuration. |
253 | addTask('gherkinlint', grunt); | |
254 | addTask('ignorefiles', grunt); | |
65d070ae | 255 | |
61fca0e0 AN |
256 | addTask('javascript', grunt); |
257 | addTask('style', grunt); | |
fa073102 | 258 | addTask('componentlibrary', grunt); |
adeb96d2 | 259 | |
61fca0e0 AN |
260 | addTask('watch', grunt); |
261 | addTask('startup', grunt); | |
adeb96d2 DW |
262 | |
263 | // Register the default task. | |
264 | grunt.registerTask('default', ['startup']); | |
265 | }; |