* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
+/* eslint-env node */
+
/**
- * Grunt configuration
+ * Calculate the cwd, taking into consideration the `root` option (for Windows).
+ *
+ * @param {Object} grunt
+ * @returns {String} The current directory as best we can determine
*/
+const getCwd = grunt => {
+ const fs = require('fs');
+ const path = require('path');
-/* eslint-env node */
-module.exports = function(grunt) {
- var path = require('path'),
- tasks = {},
- cwd = process.env.PWD || process.cwd(),
- async = require('async'),
- DOMParser = require('xmldom').DOMParser,
- xpath = require('xpath'),
- semver = require('semver'),
- watchman = require('fb-watchman'),
- watchmanClient = new watchman.Client(),
- gruntFilePath = process.cwd();
-
- // Verify the node version is new enough.
- var expected = semver.validRange(grunt.file.readJSON('package.json').engines.node);
- var actual = semver.valid(process.version);
- if (!semver.satisfies(actual, expected)) {
- grunt.fail.fatal('Node version not satisfied. Require ' + expected + ', version installed: ' + actual);
- }
+ let cwd = fs.realpathSync(process.env.PWD || process.cwd());
// Windows users can't run grunt in a subdirectory, so allow them to set
// the root by passing --root=path/to/dir.
if (grunt.option('root')) {
- var root = grunt.option('root');
+ const root = grunt.option('root');
if (grunt.file.exists(__dirname, root)) {
- cwd = path.join(__dirname, root);
+ cwd = fs.realpathSync(path.join(__dirname, root));
grunt.log.ok('Setting root to ' + cwd);
} else {
grunt.fail.fatal('Setting root to ' + root + ' failed - path does not exist');
}
}
+ return cwd;
+};
+
+/**
+ * Grunt configuration.
+ *
+ * @param {Object} grunt
+ */
+module.exports = function(grunt) {
+ const path = require('path');
+ const tasks = {};
+ const async = require('async');
+ const DOMParser = require('xmldom').DOMParser;
+ const xpath = require('xpath');
+ const semver = require('semver');
+ const watchman = require('fb-watchman');
+ const watchmanClient = new watchman.Client();
+ const fs = require('fs');
+ const ComponentList = require(path.resolve('GruntfileComponents.js'));
+
+ // Verify the node version is new enough.
+ var expected = semver.validRange(grunt.file.readJSON('package.json').engines.node);
+ var actual = semver.valid(process.version);
+ if (!semver.satisfies(actual, expected)) {
+ grunt.fail.fatal('Node version not satisfied. Require ' + expected + ', version installed: ' + actual);
+ }
+
+ // Detect directories:
+ // * gruntFilePath The real path on disk to this Gruntfile.js
+ // * cwd The current working directory, which can be overridden by the `root` option
+ // * relativeCwd The cwd, relative to the Gruntfile.js
+ // * componentDirectory The root directory of the component if the cwd is in a valid component
+ // * inComponent Whether the cwd is in a valid component
+ // * runDir The componentDirectory or cwd if not in a component, relative to Gruntfile.js
+ // * fullRunDir The full path to the runDir
+ const gruntFilePath = fs.realpathSync(process.cwd());
+ const cwd = getCwd(grunt);
+ const relativeCwd = cwd.replace(new RegExp(`${gruntFilePath}/?`), '');
+ const componentDirectory = ComponentList.getOwningComponentDirectory(relativeCwd);
+ const inComponent = !!componentDirectory;
+ const runDir = inComponent ? componentDirectory : relativeCwd;
+ const fullRunDir = fs.realpathSync(gruntFilePath + path.sep + runDir);
+ grunt.log.debug(`The cwd was detected as ${cwd} with a fullRunDir of ${fullRunDir}`);
+
+ if (inComponent) {
+ grunt.log.ok(`Running tasks for component directory ${componentDirectory}`);
+ }
+
var files = null;
if (grunt.option('files')) {
// Accept a comma separated list of files to process.
nospawn: true // We need not to spawn so config can be changed dynamically.
},
amd: {
- files: ['**/amd/src/**/*.js'],
+ files: inComponent
+ ? ['amd/src/*.js', 'amd/src/**/*.js']
+ : ['**/amd/src/**/*.js'],
tasks: ['amd']
},
boost: {
--- /dev/null
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Helper functions for working with Moodle component names, directories, and sources.
+ *
+ * @copyright 2019 Andrew Nicols <andrew@nicols.co.uk>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+"use strict";
+/* eslint-env node */
+
+/** @var {Object} A list of subsystems in Moodle */
+const componentData = {};
+
+/**
+ * Load details of all moodle modules.
+ *
+ * @returns {object}
+ */
+const fetchComponentData = () => {
+ const fs = require('fs');
+ const path = require('path');
+ const glob = require('glob');
+ const gruntFilePath = process.cwd();
+
+ if (!Object.entries(componentData).length) {
+ componentData.subsystems = {};
+ componentData.pathList = [];
+
+ // Fetch the component definiitions from the distributed JSON file.
+ const components = JSON.parse(fs.readFileSync(`${gruntFilePath}/lib/components.json`));
+
+ // Build the list of moodle subsystems.
+ componentData.subsystems.lib = 'core';
+ componentData.pathList.push(process.cwd() + path.sep + 'lib');
+ for (const [component, thisPath] of Object.entries(components.subsystems)) {
+ if (thisPath) {
+ // Prefix "core_" to the front of the subsystems.
+ componentData.subsystems[thisPath] = `core_${component}`;
+ componentData.pathList.push(process.cwd() + path.sep + thisPath);
+ }
+ }
+
+ // The list of components incldues the list of subsystems.
+ componentData.components = componentData.subsystems;
+
+ // Go through each of the plugintypes.
+ Object.entries(components.plugintypes).forEach(([pluginType, pluginTypePath]) => {
+ // We don't allow any code in this place..?
+ glob.sync(`${pluginTypePath}/*/version.php`).forEach(versionPath => {
+ const componentPath = fs.realpathSync(path.dirname(versionPath));
+ const componentName = path.basename(componentPath);
+ const frankenstyleName = `${pluginType}_${componentName}`;
+ componentData.components[`${pluginTypePath}/${componentName}`] = frankenstyleName;
+ componentData.pathList.push(componentPath);
+
+ // Look for any subplugins.
+ const subPluginConfigurationFile = `${componentPath}/db/subplugins.json`;
+ if (fs.existsSync(subPluginConfigurationFile)) {
+ const subpluginList = JSON.parse(fs.readFileSync(fs.realpathSync(subPluginConfigurationFile)));
+
+ Object.entries(subpluginList.plugintypes).forEach(([subpluginType, subpluginTypePath]) => {
+ glob.sync(`${subpluginTypePath}/*/version.php`).forEach(versionPath => {
+ const componentPath = fs.realpathSync(path.dirname(versionPath));
+ const componentName = path.basename(componentPath);
+ const frankenstyleName = `${subpluginType}_${componentName}`;
+
+ componentData.components[`${subpluginTypePath}/${componentName}`] = frankenstyleName;
+ componentData.pathList.push(componentPath);
+ });
+ });
+ }
+ });
+ });
+
+ }
+
+ return componentData;
+};
+
+/**
+ * Get the list of paths to build AMD sources.
+ *
+ * @returns {Array}
+ */
+const getAmdSrcGlobList = () => {
+ const globList = [];
+ fetchComponentData().pathList.forEach(componentPath => {
+ globList.push(`${componentPath}/amd/src/*.js`);
+ globList.push(`${componentPath}/amd/src/**/*.js`);
+ });
+
+ return globList;
+};
+
+/**
+ * Find the name of the component matching the specified path.
+ *
+ * @param {String} path
+ * @returns {String|null} Name of matching component.
+ */
+const getComponentFromPath = path => {
+ const componentList = fetchComponentData().components;
+
+ if (componentList.hasOwnProperty(path)) {
+ return componentList[path];
+ }
+
+ return null;
+};
+
+/**
+ * Check whether the supplied path, relative to the Gruntfile.js, is in a known component.
+ *
+ * @param {String} checkPath The path to check
+ * @returns {String|null}
+ */
+const getOwningComponentDirectory = checkPath => {
+ const path = require('path');
+
+ const pathList = fetchComponentData().components;
+ for (const componentPath of Object.keys(pathList)) {
+ if (checkPath === componentPath) {
+ return componentPath;
+ }
+ if (checkPath.startsWith(componentPath + path.sep)) {
+ return componentPath;
+ }
+ }
+
+ return null;
+};
+
+module.exports = {
+ getAmdSrcGlobList,
+ getComponentFromPath,
+ getOwningComponentDirectory,
+};
module.exports = ({template, types}) => {
const fs = require('fs');
const path = require('path');
- const glob = require('glob');
const cwd = process.cwd();
-
- // Static variable to hold the modules.
- let moodleSubsystems = null;
- let moodlePlugins = null;
-
- /**
- * Parse Moodle's JSON files containing the lists of components.
- *
- * The values are stored in the static variables because we
- * only need to load them once per transpiling run.
- */
- function loadMoodleModules() {
- moodleSubsystems = {'lib': 'core'};
- moodlePlugins = {};
- let components = fs.readFileSync('lib/components.json');
- components = JSON.parse(components);
-
- for (const [component, path] of Object.entries(components.subsystems)) {
- if (path) {
- // Prefix "core_" to the front of the subsystems.
- moodleSubsystems[path] = `core_${component}`;
- }
- }
-
- for (const [component, path] of Object.entries(components.plugintypes)) {
- if (path) {
- moodlePlugins[path] = component;
- }
- }
-
- for (const file of glob.sync('**/db/subplugins.json')) {
- var rawContents = fs.readFileSync(file);
- var subplugins = JSON.parse(rawContents);
-
- for (const [component, path] of Object.entries(subplugins.plugintypes)) {
- if (path) {
- moodlePlugins[path] = component;
- }
- }
- }
- }
+ const ComponentList = require(path.resolve('GruntfileComponents.js'));
/**
* Search the list of components that match the given file name
const fileName = file.replace('.js', '');
// Check subsystems first which require an exact match.
- if (moodleSubsystems.hasOwnProperty(componentPath)) {
- return `${moodleSubsystems[componentPath]}/${fileName}`;
- }
-
- // It's not a subsystem so it must be a plugin. Moodle defines root folders
- // where plugins can be installed so our path with be <plugin_root>/<plugin_name>.
- // Let's separate the two.
- let pathParts = componentPath.split('/');
- const pluginName = pathParts.pop();
- const pluginPath = pathParts.join('/');
-
- // The plugin path mutch match exactly because some plugins are subplugins of
- // other plugins which means their paths would partially match.
- if (moodlePlugins.hasOwnProperty(pluginPath)) {
- return `${moodlePlugins[pluginPath]}_${pluginName}/${fileName}`;
+ const componentName = ComponentList.getComponentFromPath(componentPath);
+ if (componentName) {
+ return `${componentName}/${fileName}`;
}
// This matches the previous PHP behaviour that would throw an exception
// if it couldn't parse an AMD file.
- throw new Error('Unable to find module name for ' + searchFileName);
+ throw new Error(`Unable to find module name for ${searchFileName} (${componentPath}::${file}}`);
}
/**
pre() {
this.seenDefine = false;
this.addedReturnForDefaultExport = false;
-
- if (moodleSubsystems === null) {
- loadMoodleModules();
- }
},
visitor: {
// Plugin ordering is only respected if we visit the "Program" node.