Skip to content

Commit

Permalink
MDL-76802 core: Create a grunt task to get upgradable libs
Browse files Browse the repository at this point in the history
A new grunt task, upgradablelibs, has been added in order to get the
list of libraries that have a newer version in their repositories.

Co-author: Andrew Lyons <andrew@moodle.com>
  • Loading branch information
sarjona committed Mar 13, 2023
1 parent edcfb3b commit f0a20c0
Show file tree
Hide file tree
Showing 6 changed files with 215 additions and 0 deletions.
150 changes: 150 additions & 0 deletions .grunt/components.js
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,155 @@ const getOwningComponentDirectory = checkPath => {
return null;
};

/**
* Get the latest tag in a remote GitHub repository.
*
* @param {string} url The remote repository.
* @returns {Array}
*/
const getRepositoryTags = async(url) => {
const gtr = require('git-tags-remote');
try {
const tags = await gtr.get(url);
if (tags !== undefined) {
return tags;
}
} catch (error) {
return [];
}
return [];
};

/**
* Get the list of thirdparty libraries that could be upgraded.
*
* @returns {Array}
*/
const getThirdPartyLibsUpgradable = async() => {
const libraries = getThirdPartyLibsData().filter((library) => !!library.repository);
const upgradableLibraries = [];
const versionCompare = (a, b) => {
if (a === b) {
return 0;
}

const aParts = a.split('.');
const bParts = b.split('.');

for (let i = 0; i < Math.min(aParts.length, bParts.length); i++) {
const aPart = parseInt(aParts[i], 10);
const bPart = parseInt(bParts[i], 10);
if (aPart > bPart) {
// 1.1.0 > 1.0.9
return 1;
} else if (aPart < bPart) {
// 1.0.9 < 1.1.0
return -1;
} else {
// Same version.
continue;
}
}

if (aParts.length > bParts.length) {
// 1.0.1 > 1.0
return 1;
}

// 1.0 < 1.0.1
return -1;
};

for (let library of libraries) {
upgradableLibraries.push(
getRepositoryTags(library.repository).then((tagMap) => {
library.version = library.version.replace(/^v/, '');
const currentVersion = library.version.replace(/moodle-/, '');
const currentMajorVersion = library.version.split('.')[0];
const tags = [...tagMap]
.map((tagData) => tagData[0])
.filter((tag) => !tag.match(/(alpha|beta|rc)/))
.map((tag) => tag.replace(/^v/, ''))
.sort((a, b) => versionCompare(b, a));
if (!tags.length) {
library.warning = "Unable to find any comparable tags.";
return library;
}

library.latestVersion = tags[0];
tags.some((tag) => {
if (!tag) {
return false;
}

// See if the version part matches.
const majorVersion = tag.split('.')[0];
if (majorVersion === currentMajorVersion) {
library.latestSameMajorVersion = tag;
return true;
}
return false;
});


if (versionCompare(currentVersion, library.latestVersion) > 0) {
// Moodle somehow has a newer version than the latest version.
library.warning = `Newer version found: ${currentVersion} > ${library.latestVersion} for ${library.name}`;
return library;
}


if (library.version !== library.latestVersion) {
// Delete version and add it again at the end of the array. That way, current and new will stay closer.
delete library.version;
library.version = currentVersion;
return library;
}
return null;
})
);
}

return (await Promise.all(upgradableLibraries)).filter((library) => !!library);
};

/**
* Get the list of thirdparty libraries.
*
* @returns {Array}
*/
const getThirdPartyLibsData = () => {
const DOMParser = require('xmldom').DOMParser;
const fs = require('fs');
const xpath = require('xpath');
const path = require('path');

const libraryList = [];
const libraryFields = [
'location',
'name',
'version',
'repository',
];

const thirdpartyfiles = getThirdPartyLibsList(fs.realpathSync('./'));
thirdpartyfiles.forEach(function(libraryPath) {
const xmlContent = fs.readFileSync(libraryPath, 'utf8');
const doc = new DOMParser().parseFromString(xmlContent);
const libraries = xpath.select("/libraries/library", doc);
for (const library of libraries) {
const libraryData = [];
for (const field of libraryFields) {
libraryData[field] = xpath.select(`${field}/text()`, library)?.toString();
}
libraryData.location = path.join(path.dirname(libraryPath), libraryData.location);
libraryList.push(libraryData);
}
});

return libraryList.sort((a, b) => a.location.localeCompare(b.location));
};

module.exports = {
fetchComponentData,
getAmdSrcGlobList,
Expand All @@ -241,4 +390,5 @@ module.exports = {
getYuiSrcGlobList,
getThirdPartyLibsList,
getThirdPartyPaths,
getThirdPartyLibsUpgradable,
};
42 changes: 42 additions & 0 deletions .grunt/tasks/upgradablelibs.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// 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/>.
/* jshint node: true, browser: false */
/* eslint-env node */

/**
* @copyright 2023 Sara Arjona <sara@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/

module.exports = grunt => {
/**
* Generate upgradable third-party libraries (utilising thirdpartylibs.xml data)
*/
grunt.registerTask('upgradablelibs', 'Generate upgradable third-party libraries', async function() {
const done = this.async();

const path = require('path');
const ComponentList = require(path.join(process.cwd(), '.grunt', 'components.js'));

// An array of third party libraries that have a newer version in their repositories.
const thirdPartyLibs = await ComponentList.getThirdPartyLibsUpgradable({progress: true});
for (let library of thirdPartyLibs) {
grunt.log.ok(JSON.stringify(Object.assign({}, library), null, 4));
}

done();
});

};
2 changes: 2 additions & 0 deletions Gruntfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,8 @@ module.exports = function(grunt) {
addTask('watch', grunt);
addTask('startup', grunt);

addTask('upgradablelibs', grunt);

// Register the default task.
grunt.registerTask('default', ['startup']);
};
1 change: 1 addition & 0 deletions lib/upgrade.txt
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ information provided here is intended especially for developers.
Course formats using components will be allowed to use one level indentation only.
* The method `flexible_table::set_columnsattributes` now can be used with 'class' key to add custom classes to the DOM.
* The editor_tinymce plugin has been removed from core.
* A new grunt task, upgradablelibs, has been added to get the list of libraries that have a newer version in their repositories.

=== 4.1 ===

Expand Down
19 changes: 19 additions & 0 deletions npm-shrinkwrap.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"eslint-plugin-promise": "6.0.0",
"fb-watchman": "2.0.1",
"gherkin-lint": "^4.2.2",
"git-tags-remote": "^1.0.5",
"glob": "7.2.0",
"grunt": "^1.4.1",
"grunt-contrib-uglify": "5.0.1",
Expand Down

0 comments on commit f0a20c0

Please sign in to comment.