From cde6b5514080138bcfbf0331b17c870c5a9cfa77 Mon Sep 17 00:00:00 2001 From: Guy Sartorelli <36352093+GuySartorelli@users.noreply.github.com> Date: Mon, 6 May 2024 18:10:45 +1200 Subject: [PATCH] Provide a PHP API for reasoning about modules, branches, and releases (#28) * NEW Make a unified JSON file for all CMS versions * NEW Migrate logic from workflows for reasoning about repository metadata --- .editorconfig | 16 + .github/workflows/ci.yml | 11 + .gitignore | 2 + README.md | 46 +- composer.json | 17 + gha-no-status.svg | 35 - index.html | 24 - labels.json | 41 - main.css | 48 -- main.js | 87 -- modules.json | 871 -------------------- phpunit.xml | 8 + repositories.json | 1576 +++++++++++++++++++++++++++++++++++++ src/BranchLogic.php | 247 ++++++ src/MetaData.php | 165 ++++ templates/LICENSE.md | 29 - tests/BranchLogicTest.php | 846 ++++++++++++++++++++ tests/MetaDataTest.php | 231 ++++++ 18 files changed, 3142 insertions(+), 1158 deletions(-) create mode 100644 .editorconfig create mode 100644 .github/workflows/ci.yml create mode 100644 .gitignore create mode 100644 composer.json delete mode 100644 gha-no-status.svg delete mode 100644 index.html delete mode 100644 labels.json delete mode 100644 main.css delete mode 100644 main.js delete mode 100644 modules.json create mode 100644 phpunit.xml create mode 100644 repositories.json create mode 100644 src/BranchLogic.php create mode 100644 src/MetaData.php delete mode 100644 templates/LICENSE.md create mode 100644 tests/BranchLogicTest.php create mode 100644 tests/MetaDataTest.php diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..50eff41 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,16 @@ +# For more information about the properties used in +# this file, please see the EditorConfig documentation: +# http://editorconfig.org/ + +root = true + +[*] +charset = utf-8 +end_of_line = lf +indent_size = 4 +indent_style = space +insert_final_newline = true +trim_trailing_whitespace = true + +[*.md] +trim_trailing_whitespace = false diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..bf02210 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,11 @@ +name: CI + +on: + push: + pull_request: + workflow_dispatch: + +jobs: + ci: + name: CI + uses: silverstripe/gha-ci/.github/workflows/ci.yml@v1 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..55940e5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/vendor/ +composer.lock \ No newline at end of file diff --git a/README.md b/README.md index 71fcc38..721de9b 100644 --- a/README.md +++ b/README.md @@ -1,37 +1,37 @@ # Silverstripe CMS Supported Modules Metadata -Used to generate the -[supported modules list](https://www.silverstripe.org/software/addons/silverstripe-commercially-supported-module-list/) on silverstripe.org, -and is the starting point for tooling such as -our ["Elvis" bug tracker](https://github.com/silverstripe/github-issue-search-client). +Metadata and some supporting PHP logic for determining which branches of various GitHub repositories relate to which versions of Silverstripe CMS. -Each branch of this repository represents a major release line of Silverstripe CMS. You can fetch the JSON for the relevant release line by simply fetching the raw copy of `modules.json` for a given release branch, e.g. https://raw.githubusercontent.com/silverstripe/supported-modules/5/modules.json +> [!IMPORTANT] +> Only the `main` branch of this repository is maintained. -It's known to be used in the following repositories: - -- [silverstripe/cow](https://github.com/silverstripe/cow) -- [silverstripe/tx-translator](https://github.com/silverstripe/silverstripe-tx-translator/) -- [bringyourownideas/silverstripe-maintainence](https://github.com/bringyourownideas/silverstripe-maintenance) -- [silverstripe/github-issue-search-client](https://github.com/silverstripe/github-issue-search-client) -- [silverstripe/module-standardiser](https://github.com/silverstripe/module-standardiser) +You can fetch the JSON by simply fetching the raw copy of `repositories.json` file, e.g. , though you're encouraged to use composer to pull in the data instead where appropriate. ## Format - * `github`: String. Github repository name (incl. org) - * `gitlab`: String. Alternative gitlab repository name (incl. org) - * `composer`: String. Packagist/composer name - * `scrutinizer`: Boolean. Does this repo have Scrutinizer enabled? - * `addons`: Boolean. Does this module exist on addons.silverstripe.org? - * `type`: String. `supported-module` or `supported-dependency` - * `githubId`: Number. The [id](https://docs.github.com/en/rest/reference/repos#get-a-repository) in Github. Used as a unique identifier. - * `isCore`: Boolean. Is this considered a direct dependency of `silverstripe/installer`, `silverstripe/recipe-cms` or `silverstripe/recipe-core`? - * `branches`: Array<String>. All major branches in lowest-to-heighest order (e.g. `["3", "4"]`, not `["4", "4.12"]`) of this module which are officially supported for this major release line of Silverstripe CMS. E.g. silverstripe/graphql was supported for `3` and `4` for the CMS 4 major release line. - * Systems using the branches array need to be smart enough to check for last-minor branches if the branch in the list is missing from github (e.g. if `4` is missing, fetch the list of branches for that repository from the github API and use the latest `4.x` (e.g. `4.13`) branch). +There are several sections in the `repositories.json` file, denoting different categories of repositories: + +- `supportedModules`: Repositories representing supported modules. If cow cares about it, it should probably be in this category. +- `workflow`: Repositories which hold GitHub actions and workflows. +- `tooling`: Repositories used to help streamline Silverstripe CMS maintenance +- `misc`: All repositories we need to track which don't fit in one of the above categories. + +Each of the above sections holds an array of JSON objects with the following data: + +|key|type|description| +|---|---|---| +|`github`|_String_|Github repository name (incl. org)| +|`packagist`|_String_|Packagist name. Only relevant if the repo isn't registered in packagist - otherwise null.| +|`githubId`|_Number_|The [id](https://docs.github.com/en/rest/reference/repos#get-a-repository) in Github. Used as a unique identifier.| +|`isCore`|_Boolean_|Is this considered a direct dependency of `silverstripe/installer`, `silverstripe/recipe-cms` or `silverstripe/recipe-core`? (Only relevant for supported modules)| +|`lockstepped`|_Boolean_|Whether this is _always_ given a new minor release in "lock step" with Silverstripe CMS as a whole. (Only relevant for supported modules)| +|`type`|_String_|One of "module", "recipe", "theme", or "other". (Only relevant for supported modules)| +|`majorVersionMapping`|_Object_|A map of major versions, with the Silverstripe CMS major release lines as object keys and an array of all matching major release lines for the repository as values.
• The repository versions are branch names, but in most cases these will map to a major release line (e.g. "5" branch which represents the "5.x" release line)
• If a `"*"` key is present, it should be used for any CMS major release lines which do not have their own keys.
• If a CMS major release line is missing, and there is no `"*"` key, the repository should be ignored for that CMS major release line.
• If the value is an empty array, the default branch should be used.| ## Adding a repo You can easily retrieve the `githubId` via the following API call: -``` +```text https://api.github.com/repos/my-org/my-repo ``` diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..5154dc0 --- /dev/null +++ b/composer.json @@ -0,0 +1,17 @@ +{ + "name": "silverstripe/supported-modules", + "description": "Metadata about Silverstripe CMS supported modules and other repositories maintained by Silverstripe", + "autoload": { + "psr-4": { + "SilverStripe\\SupportedModules\\": "src/", + "SilverStripe\\SupportedModules\\Tests\\": "tests/" + } + }, + "require": { + "php": "^8.1", + "composer/semver": "^3.4" + }, + "require-dev": { + "phpunit/phpunit": "^9.6" + } +} diff --git a/gha-no-status.svg b/gha-no-status.svg deleted file mode 100644 index cd6ccaf..0000000 --- a/gha-no-status.svg +++ /dev/null @@ -1,35 +0,0 @@ - - CI - no status - - - - - - - - - - - - - - - - - - CI - - - - - - - no status - - - - - - diff --git a/index.html b/index.html deleted file mode 100644 index bf00935..0000000 --- a/index.html +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - -

SilverStripe Ltd. commits to commercially supporting and bringing up to a specified module standard the below list of modules. We've come up with some criteria for the supported module standard - - - - - - - - - - - -
ModuleTypeProgress
- - - - diff --git a/labels.json b/labels.json deleted file mode 100644 index 2bf72eb..0000000 --- a/labels.json +++ /dev/null @@ -1,41 +0,0 @@ -{ - "default_labels": { - "affects/v3": "d4c5f9", - "affects/v4": "5319e7", - "affects/v5": "0e8a16", - "change/major": "e99695", - "change/minor": "f9d0c4", - "change/patch": "fef2c0", - "effort/easy": "c2e0c6", - "effort/hard": "f9d0c4", - "effort/medium": "fef2c0", - "feedback-required/author": "DDDDDD", - "feedback-required/core-team": "DDDDDD", - "impact/critical": "e11d21", - "impact/high": "eb6420", - "impact/low": "fef2c0", - "impact/medium": "f7c6c7", - "rfc/accepted": "DDDDDD", - "rfc/draft": "DDDDDD", - "type/bug": "d93f0b", - "type/docs": "02d7e1", - "type/enhancement": "0e8a16", - "type/frontend": "c5def5", - "type/userhelp": "c5def5", - "type/UX": "006b75" - }, - "rename_labels": { - "bug": "type/bug", - "critical": "impact/critical", - "enhancement": "type/enhancement" - }, - "remove_labels": [ - "good first issue", - "wontfix", - "question", - "invalid", - "help wanted", - "duplicate", - "in progress" - ] -} diff --git a/main.css b/main.css deleted file mode 100644 index 7cb4d12..0000000 --- a/main.css +++ /dev/null @@ -1,48 +0,0 @@ -body { - margin: 0; - padding: 25px; - cursor: default; - font-family: "Helvetica", "Arial"; - font-size: 15px; - color: #333; -} - -a { - text-decoration: none; -} - -table { - border-collapse: collapse; - width: 100%; - cellpadding: 1; -} - -th, td { - text-align: left; - padding: 5px; - border: solid 1px #ccc; - vertical-align: middle; -} - -img { - vertical-align: middle; -} - -tbody tr:nth-child(2n+1) { - background: #eee; -} - -.progress { - padding-left: 0; - border-left-width: 0; - border-right-width: 0; -} - -.progress.first { - padding-left: 5px; - border-left-width: 1px; -} - -.progress.last { - border-right-width: 1px; -} diff --git a/main.js b/main.js deleted file mode 100644 index 8342e14..0000000 --- a/main.js +++ /dev/null @@ -1,87 +0,0 @@ -var no_status = 'https://raw.githubusercontent.com/silverstripe/supported-modules/gh-pages/gha-no-status.svg'; - -$.ajax({ - "url": "modules.json", - "dataType": "json" -}).then(function(modules) { - var rows = []; - - modules.forEach(function(module) { - var row = ""; - - if (module.addons) { - row += "
" + module.composer + ""; - } else { - if (module.github) { - row += "" + module.composer + ""; - } else if (module.gitlab) { - row += "" + module.composer + ""; - } else { - row += "" + module.composer + ""; - } - } - - if (module.type === "supported-module") { - row += "" + "Supported module" + ""; - } else { - row += "" + "Supported dependency" + ""; - } - - if (module.github) { - row += [ - "", - "", - "", - "", - "", - "", - "", - "" - ].join(''); - } else if (module.gitlab) { - row += "Module on Gitlab"; - } else { - row += "Module definition incomplete"; - } - - row += ""; - - rows.push(row); - }); - - $("tbody").html(rows.join("")); -}); - -// this script is to replace the "missing image" placeholder with a "no status" ci badge - -var c = 0; -var interval = setInterval(function() { - var els = document.querySelectorAll('.progress'); - els.forEach(function(el) { - var noci = el.querySelector('.noci'); - if (!noci) { - return; - } - var ci = el.querySelector('.ci'); - var style = getComputedStyle(ci); - // ci badge has loaded - // using 50 because the "missing image" browser placeholder actually has a computed width - if (style.width.replace('px', '') > 50) { - el.removeChild(noci.parentNode); - ci.parentNode.style.removeProperty('visibility'); - } - }); - // give it 10 seconds to fetch status badges - if (c++ >= 4 * 10) { - // delete missing ci elements - els.forEach(function(el) { - var noci = el.querySelector('.noci'); - if (!noci) { - return; - } - var ci = el.querySelector('.ci'); - el.removeChild(ci.parentNode); - }); - clearInterval(interval); - } -}, 250); diff --git a/modules.json b/modules.json deleted file mode 100644 index 0e927b1..0000000 --- a/modules.json +++ /dev/null @@ -1,871 +0,0 @@ -[ - { - "github": "bringyourownideas/silverstripe-maintenance", - "gitlab": null, - "composer": "bringyourownideas/silverstripe-maintenance", - "scrutinizer": true, - "addons": true, - "type": "supported-module", - "githubId": 42240917, - "isCore": false, - "branches": ["3"] - }, - { - "github": "bringyourownideas/silverstripe-composer-update-checker", - "gitlab": null, - "composer": "bringyourownideas/silverstripe-composer-update-checker", - "scrutinizer": true, - "addons": true, - "type": "supported-module", - "githubId": 41240800, - "isCore": false, - "branches": ["4"] - }, - { - "github": "colymba/GridFieldBulkEditingTools", - "gitlab": null, - "composer": "colymba/gridfield-bulk-editing-tools", - "scrutinizer": false, - "addons": true, - "type": "supported-module", - "githubId": 5071848, - "isCore": false, - "branches": ["4"] - }, - { - "github": "silverstripe/cwp-agencyextensions", - "gitlab": null, - "composer": "cwp/agency-extensions", - "scrutinizer": true, - "addons": true, - "type": "supported-module", - "githubId": 113399978, - "isCore": false, - "branches": ["3"] - }, - { - "github": "silverstripe/cwp-starter-theme", - "gitlab": null, - "composer": "cwp/starter-theme", - "scrutinizer": false, - "addons": true, - "type": "supported-module", - "githubId": 109077240, - "isCore": false, - "branches": ["4"] - }, - { - "github": "silverstripe/developer-docs", - "gitlab": null, - "composer": "silverstripe/developer-docs", - "scrutinizer": false, - "addons": false, - "type": "supported-module", - "githubId": 510980223, - "isCore": true, - "branches": ["5"] - }, - { - "github": "silverstripe/cwp-watea-theme", - "gitlab": null, - "composer": "cwp/watea-theme", - "scrutinizer": false, - "addons": true, - "type": "supported-module", - "githubId": 109077377, - "isCore": false, - "branches": ["4"] - }, - { - "github": "silverstripe/silverstripe-elemental", - "gitlab": null, - "composer": "dnadesign/silverstripe-elemental", - "scrutinizer": true, - "addons": true, - "type": "supported-module", - "githubId": 23339883, - "isCore": false, - "branches": ["5"] - }, - { - "github": "dnadesign/silverstripe-elemental-userforms", - "gitlab": null, - "composer": "dnadesign/silverstripe-elemental-userforms", - "scrutinizer": true, - "addons": true, - "type": "supported-module", - "githubId": 96047938, - "isCore": false, - "branches": ["4"] - }, - { - "github": "silverstripe/silverstripe-admin", - "gitlab": null, - "composer": "silverstripe/admin", - "scrutinizer": false, - "addons": true, - "type": "supported-module", - "githubId": 84500508, - "isCore": true, - "branches": ["2"] - }, - { - "github": "silverstripe/silverstripe-asset-admin", - "gitlab": null, - "composer": "silverstripe/asset-admin", - "scrutinizer": true, - "addons": true, - "type": "supported-module", - "githubId": 42913926, - "isCore": true, - "branches": ["2"] - }, - { - "github": "silverstripe/silverstripe-assets", - "gitlab": null, - "composer": "silverstripe/assets", - "scrutinizer": false, - "addons": true, - "type": "supported-module", - "githubId": 85148184, - "isCore": true, - "branches": ["2"] - }, - { - "github": "silverstripe/silverstripe-auditor", - "gitlab": null, - "composer": "silverstripe/auditor", - "scrutinizer": true, - "addons": true, - "type": "supported-module", - "githubId": 47799024, - "isCore": false, - "branches": ["3"] - }, - { - "github": "silverstripe/silverstripe-blog", - "gitlab": null, - "composer": "silverstripe/blog", - "scrutinizer": true, - "addons": true, - "type": "supported-module", - "githubId": 1236910, - "isCore": false, - "branches": ["4"] - }, - { - "github": "silverstripe/silverstripe-campaign-admin", - "gitlab": null, - "composer": "silverstripe/campaign-admin", - "scrutinizer": true, - "addons": true, - "type": "supported-module", - "githubId": 85750633, - "isCore": true, - "branches": ["2"] - }, - { - "github": "silverstripe/silverstripe-cms", - "gitlab": null, - "composer": "silverstripe/cms", - "scrutinizer": true, - "addons": true, - "type": "supported-module", - "githubId": 1319183, - "isCore": true, - "branches": ["5"] - }, - { - "github": "silverstripe/silverstripe-config", - "gitlab": null, - "composer": "silverstripe/config", - "scrutinizer": true, - "addons": false, - "type": "supported-module", - "githubId": 66067831, - "isCore": true, - "branches": ["2"] - }, - { - "github": "silverstripe/silverstripe-contentreview", - "gitlab": null, - "composer": "silverstripe/contentreview", - "scrutinizer": true, - "addons": true, - "type": "supported-module", - "githubId": 2370478, - "isCore": false, - "branches": ["5"] - }, - { - "github": "silverstripe/silverstripe-crontask", - "gitlab": null, - "composer": "silverstripe/crontask", - "scrutinizer": true, - "addons": true, - "type": "supported-module", - "githubId": 12394679, - "isCore": false, - "branches": ["3"] - }, - { - "github": "silverstripe/silverstripe-documentconverter", - "gitlab": null, - "composer": "silverstripe/documentconverter", - "scrutinizer": true, - "addons": true, - "type": "supported-module", - "githubId": 113400338, - "isCore": false, - "branches": ["3"] - }, - { - "github": "silverstripe/silverstripe-elemental-bannerblock", - "gitlab": null, - "composer": "silverstripe/elemental-bannerblock", - "scrutinizer": true, - "addons": true, - "type": "supported-module", - "githubId": 136992112, - "isCore": false, - "branches": ["3"] - }, - { - "github": "silverstripe/silverstripe-elemental-fileblock", - "gitlab": null, - "composer": "silverstripe/elemental-fileblock", - "scrutinizer": true, - "addons": true, - "type": "supported-module", - "githubId": 136990365, - "isCore": false, - "branches": ["3"] - }, - { - "github": "silverstripe/silverstripe-environmentcheck", - "gitlab": null, - "composer": "silverstripe/environmentcheck", - "scrutinizer": true, - "addons": true, - "type": "supported-module", - "githubId": 3143218, - "isCore": false, - "branches": ["3"] - }, - { - "github": "silverstripe/silverstripe-errorpage", - "gitlab": null, - "composer": "silverstripe/errorpage", - "scrutinizer": false, - "addons": true, - "type": "supported-module", - "githubId": 94210313, - "isCore": true, - "branches": ["2"] - }, - { - "github": "silverstripe/eslint-config", - "gitlab": null, - "composer": "silverstripe/eslint-config", - "scrutinizer": false, - "addons": false, - "type": "supported-dependency", - "githubId": 109643040, - "isCore": false, - "branches": ["1"] - }, - { - "github": "silverstripe/silverstripe-externallinks", - "gitlab": null, - "composer": "silverstripe/externallinks", - "scrutinizer": true, - "addons": true, - "type": "supported-module", - "githubId": 22708348, - "isCore": false, - "branches": ["3"] - }, - { - "github": "silverstripe/silverstripe-framework", - "gitlab": null, - "composer": "silverstripe/framework", - "scrutinizer": true, - "addons": true, - "type": "supported-module", - "githubId": 1318892, - "isCore": true, - "branches": ["5"] - }, - { - "github": "silverstripe/silverstripe-graphql", - "gitlab": null, - "composer": "silverstripe/graphql", - "scrutinizer": false, - "addons": true, - "type": "supported-module", - "githubId": 68341446, - "isCore": true, - "branches": ["5"] - }, - { - "github": "silverstripe/silverstripe-gridfieldqueuedexport", - "gitlab": null, - "composer": "silverstripe/gridfieldqueuedexport", - "scrutinizer": true, - "addons": true, - "type": "supported-module", - "githubId": 59252430, - "isCore": false, - "branches": ["3"] - }, - { - "github": "silverstripe/silverstripe-hybridsessions", - "gitlab": null, - "composer": "silverstripe/hybridsessions", - "scrutinizer": true, - "addons": true, - "type": "supported-module", - "githubId": 22979135, - "isCore": false, - "branches": ["3"] - }, - { - "github": "silverstripe/silverstripe-iframe", - "gitlab": null, - "composer": "silverstripe/iframe", - "scrutinizer": true, - "addons": true, - "type": "supported-module", - "githubId": 4515744, - "isCore": false, - "branches": ["3"] - }, - { - "github": "silverstripe/silverstripe-installer", - "gitlab": null, - "composer": "silverstripe/installer", - "scrutinizer": true, - "addons": false, - "type": "supported-module", - "githubId": 1319402, - "isCore": true, - "branches": ["5"] - }, - { - "github": "silverstripe/silverstripe-ldap", - "gitlab": null, - "composer": "silverstripe/ldap", - "scrutinizer": true, - "addons": true, - "type": "supported-module", - "githubId": 104963133, - "isCore": false, - "branches": ["2"] - }, - { - "github": "silverstripe/silverstripe-linkfield", - "gitlab": null, - "composer": "silverstripe/linkfield", - "scrutinizer": false, - "addons": false, - "type": "supported-module", - "githubId": 315191815, - "isCore": false, - "branches": ["4"] - }, - { - "github": "silverstripe/silverstripe-lumberjack", - "gitlab": null, - "composer": "silverstripe/lumberjack", - "scrutinizer": true, - "addons": true, - "type": "supported-module", - "githubId": 30332001, - "isCore": false, - "branches": ["3"] - }, - { - "github": "silverstripe/silverstripe-mimevalidator", - "gitlab": null, - "composer": "silverstripe/mimevalidator", - "scrutinizer": true, - "addons": true, - "type": "supported-module", - "githubId": 22493606, - "isCore": false, - "branches": ["3"] - }, - { - "github": "silverstripe/silverstripe-realme", - "gitlab": null, - "composer": "silverstripe/realme", - "scrutinizer": true, - "addons": true, - "type": "supported-module", - "githubId": 46946194, - "isCore": false, - "branches": ["5"] - }, - { - "github": "silverstripe/silverstripe-session-manager", - "gitlab": null, - "composer": "silverstripe/session-manager", - "scrutinizer": false, - "addons": true, - "type": "supported-module", - "githubId": 128231892, - "isCore": true, - "branches": ["2"] - }, - { - "github": "silverstripe/recipe-authoring-tools", - "gitlab": null, - "composer": "silverstripe/recipe-authoring-tools", - "scrutinizer": false, - "addons": false, - "type": "supported-module", - "githubId": 120226694, - "isCore": false, - "branches": ["2"] - }, - { - "github": "silverstripe/recipe-blog", - "gitlab": null, - "composer": "silverstripe/recipe-blog", - "scrutinizer": false, - "addons": false, - "type": "supported-module", - "githubId": 119918895, - "isCore": false, - "branches": ["2"] - }, - { - "github": "silverstripe/recipe-cms", - "gitlab": null, - "composer": "silverstripe/recipe-cms", - "scrutinizer": false, - "addons": false, - "type": "supported-module", - "githubId": 96844605, - "isCore": true, - "branches": ["5"] - }, - { - "github": "silverstripe/recipe-collaboration", - "gitlab": null, - "composer": "silverstripe/recipe-collaboration", - "scrutinizer": false, - "addons": false, - "type": "supported-module", - "githubId": 119923751, - "isCore": false, - "branches": ["2"] - }, - { - "github": "silverstripe/recipe-content-blocks", - "gitlab": null, - "composer": "silverstripe/recipe-content-blocks", - "scrutinizer": false, - "addons": false, - "type": "supported-module", - "githubId": 120223778, - "isCore": false, - "branches": ["3"] - }, - { - "github": "silverstripe/recipe-core", - "gitlab": null, - "composer": "silverstripe/recipe-core", - "scrutinizer": false, - "addons": false, - "type": "supported-module", - "githubId": 96839278, - "isCore": true, - "branches": ["5"] - }, - { - "github": "silverstripe/recipe-form-building", - "gitlab": null, - "composer": "silverstripe/recipe-form-building", - "scrutinizer": false, - "addons": false, - "type": "supported-module", - "githubId": 120237364, - "isCore": false, - "branches": ["2"] - }, - { - "github": "silverstripe/recipe-plugin", - "gitlab": null, - "composer": "silverstripe/recipe-plugin", - "scrutinizer": false, - "addons": false, - "type": "supported-module", - "githubId": 67970412, - "isCore": true, - "branches": ["2"] - }, - { - "github": "silverstripe/recipe-reporting-tools", - "gitlab": null, - "composer": "silverstripe/recipe-reporting-tools", - "scrutinizer": false, - "addons": false, - "type": "supported-module", - "githubId": 120228554, - "isCore": false, - "branches": ["2"] - }, - { - "github": "silverstripe/recipe-services", - "gitlab": null, - "composer": "silverstripe/recipe-services", - "scrutinizer": false, - "addons": false, - "type": "supported-module", - "githubId": 120680662, - "isCore": false, - "branches": ["2"] - }, - { - "github": "silverstripe/silverstripe-registry", - "gitlab": null, - "composer": "silverstripe/registry", - "scrutinizer": true, - "addons": true, - "type": "supported-module", - "githubId": 8086664, - "isCore": false, - "branches": ["3"] - }, - { - "github": "silverstripe/silverstripe-reports", - "gitlab": null, - "composer": "silverstripe/reports", - "scrutinizer": true, - "addons": true, - "type": "supported-module", - "githubId": 7656757, - "isCore": true, - "branches": ["5"] - }, - { - "github": "silverstripe/silverstripe-restfulserver", - "gitlab": null, - "composer": "silverstripe/restfulserver", - "scrutinizer": true, - "addons": true, - "type": "supported-module", - "githubId": 4222524, - "isCore": false, - "branches": ["3"] - }, - { - "github": "silverstripe/silverstripe-securityreport", - "gitlab": null, - "composer": "silverstripe/securityreport", - "scrutinizer": true, - "addons": true, - "type": "supported-module", - "githubId": 19595761, - "isCore": false, - "branches": ["3"] - }, - { - "github": "silverstripe/silverstripe-segment-field", - "gitlab": null, - "composer": "silverstripe/segment-field", - "scrutinizer": true, - "addons": true, - "type": "supported-module", - "githubId": 40516528, - "isCore": false, - "branches": ["3"] - }, - { - "github": "silverstripe/silverstripe-sharedraftcontent", - "gitlab": null, - "composer": "silverstripe/sharedraftcontent", - "scrutinizer": true, - "addons": true, - "type": "supported-module", - "githubId": 35126267, - "isCore": false, - "branches": ["3"] - }, - { - "github": "silverstripe/silverstripe-siteconfig", - "gitlab": null, - "composer": "silverstripe/siteconfig", - "scrutinizer": true, - "addons": true, - "type": "supported-module", - "githubId": 22776092, - "isCore": true, - "branches": ["5"] - }, - { - "github": "silverstripe/silverstripe-sitewidecontent-report", - "gitlab": null, - "composer": "silverstripe/sitewidecontent-report", - "scrutinizer": true, - "addons": true, - "type": "supported-module", - "githubId": 43330250, - "isCore": false, - "branches": ["4"] - }, - { - "github": "silverstripe/silverstripe-spamprotection", - "gitlab": null, - "composer": "silverstripe/spamprotection", - "scrutinizer": true, - "addons": true, - "type": "supported-module", - "githubId": 1236936, - "isCore": false, - "branches": ["4"] - }, - { - "github": "silverstripe/silverstripe-staticpublishqueue", - "gitlab": null, - "composer": "silverstripe/staticpublishqueue", - "scrutinizer": true, - "addons": true, - "type": "supported-module", - "githubId": 9162434, - "isCore": false, - "branches": ["6"] - }, - { - "github": "silverstripe/silverstripe-subsites", - "gitlab": null, - "composer": "silverstripe/subsites", - "scrutinizer": true, - "addons": true, - "type": "supported-module", - "githubId": 1236940, - "isCore": false, - "branches": ["3"] - }, - { - "github": "silverstripe/silverstripe-tagfield", - "gitlab": null, - "composer": "silverstripe/tagfield", - "scrutinizer": true, - "addons": true, - "type": "supported-module", - "githubId": 1181344, - "isCore": false, - "branches": ["3"] - }, - { - "github": "silverstripe/silverstripe-taxonomy", - "gitlab": null, - "composer": "silverstripe/taxonomy", - "scrutinizer": true, - "addons": true, - "type": "supported-module", - "githubId": 8301510, - "isCore": false, - "branches": ["3"] - }, - { - "github": "silverstripe/silverstripe-textextraction", - "gitlab": null, - "composer": "silverstripe/textextraction", - "scrutinizer": true, - "addons": true, - "type": "supported-module", - "githubId": 7482455, - "isCore": false, - "branches": ["4"] - }, - { - "github": "silverstripe/silverstripe-userforms", - "gitlab": null, - "composer": "silverstripe/userforms", - "scrutinizer": true, - "addons": true, - "type": "supported-module", - "githubId": 1247754, - "isCore": false, - "branches": ["6"] - }, - { - "github": "silverstripe/vendor-plugin", - "gitlab": null, - "composer": "silverstripe/vendor-plugin", - "scrutinizer": true, - "addons": false, - "type": "supported-module", - "githubId": 104690866, - "isCore": true, - "branches": ["2"] - }, - { - "github": "silverstripe/silverstripe-versioned", - "gitlab": null, - "composer": "silverstripe/versioned", - "scrutinizer": false, - "addons": true, - "type": "supported-module", - "githubId": 85634633, - "isCore": true, - "branches": ["2"] - }, - { - "github": "silverstripe/silverstripe-versioned-admin", - "gitlab": null, - "composer": "silverstripe/versioned-admin", - "scrutinizer": true, - "addons": true, - "type": "supported-module", - "githubId": 124332817, - "isCore": true, - "branches": ["2"] - }, - { - "github": "silverstripe/silverstripe-versionfeed", - "gitlab": null, - "composer": "silverstripe/versionfeed", - "scrutinizer": true, - "addons": true, - "type": "supported-module", - "githubId": 6821471, - "isCore": false, - "branches": ["3"] - }, - { - "github": "silverstripe/webpack-config", - "gitlab": null, - "composer": "silverstripe/webpack-config", - "scrutinizer": false, - "addons": false, - "type": "supported-dependency", - "githubId": 92692253, - "isCore": false, - "branches": ["2"] - }, - { - "github": "silverstripe-themes/silverstripe-simple", - "gitlab": null, - "composer": "silverstripe-themes/simple", - "scrutinizer": false, - "addons": true, - "type": "supported-module", - "githubId": 3712566, - "isCore": true, - "branches": ["3"] - }, - { - "github": "symbiote/silverstripe-advancedworkflow", - "gitlab": null, - "composer": "symbiote/silverstripe-advancedworkflow", - "scrutinizer": true, - "addons": true, - "type": "supported-module", - "githubId": 981174, - "isCore": false, - "branches": ["6"] - }, - { - "github": "symbiote/silverstripe-gridfieldextensions", - "gitlab": null, - "composer": "symbiote/silverstripe-gridfieldextensions", - "scrutinizer": false, - "addons": true, - "type": "supported-module", - "githubId": 7373726, - "isCore": false, - "branches": ["4"] - }, - { - "github": "symbiote/silverstripe-multivaluefield", - "gitlab": null, - "composer": "symbiote/silverstripe-multivaluefield", - "scrutinizer": true, - "addons": true, - "type": "supported-module", - "githubId": 624044, - "isCore": false, - "branches": ["6"] - }, - { - "github": "symbiote/silverstripe-queuedjobs", - "gitlab": null, - "composer": "symbiote/silverstripe-queuedjobs", - "scrutinizer": true, - "addons": true, - "type": "supported-module", - "githubId": 660816, - "isCore": false, - "branches": ["5"] - }, - { - "github": "tractorcow-farm/silverstripe-fluent", - "gitlab": null, - "composer": "tractorcow/silverstripe-fluent", - "scrutinizer": true, - "addons": true, - "type": "supported-module", - "githubId": 10893201, - "isCore": false, - "branches": ["7"] - }, - { - "github": "silverstripe/silverstripe-mfa", - "gitlab": null, - "composer": "silverstripe/mfa", - "scrutinizer": true, - "addons": true, - "type": "supported-module", - "githubId": 172815373, - "isCore": false, - "branches": ["5"] - }, - { - "github": "silverstripe/silverstripe-totp-authenticator", - "gitlab": null, - "composer": "silverstripe/totp-authenticator", - "scrutinizer": true, - "addons": true, - "type": "supported-module", - "githubId": 179381590, - "isCore": false, - "branches": ["5"] - }, - { - "github": "silverstripe/silverstripe-webauthn-authenticator", - "gitlab": null, - "composer": "silverstripe/webauthn-authenticator", - "scrutinizer": true, - "addons": true, - "type": "supported-module", - "githubId": 176832496, - "isCore": false, - "branches": ["5"] - }, - { - "github": "silverstripe/silverstripe-login-forms", - "gitlab": null, - "composer": "silverstripe/login-forms", - "scrutinizer": true, - "addons": true, - "type": "supported-module", - "githubId": 155142697, - "isCore": false, - "branches": ["5"] - }, - { - "github": "silverstripe/silverstripe-dynamodb", - "gitlab": null, - "composer": "silverstripe/dynamodb", - "scrutinizer": true, - "addons": true, - "type": "supported-module", - "githubId": 38406904, - "isCore": false, - "branches": ["5"] - } -] diff --git a/phpunit.xml b/phpunit.xml new file mode 100644 index 0000000..d797431 --- /dev/null +++ b/phpunit.xml @@ -0,0 +1,8 @@ + + + + + tests + + + diff --git a/repositories.json b/repositories.json new file mode 100644 index 0000000..aea52fc --- /dev/null +++ b/repositories.json @@ -0,0 +1,1576 @@ +{ + "supportedModules": [ + { + "github": "bringyourownideas/silverstripe-maintenance", + "packagist": "bringyourownideas/silverstripe-maintenance", + "githubId": 42240917, + "isCore": false, + "lockstepped": false, + "type": "module", + "majorVersionMapping": { + "4": ["2"], + "5": ["3"], + "6": ["4"] + } + }, + { + "github": "bringyourownideas/silverstripe-composer-update-checker", + "packagist": "bringyourownideas/silverstripe-composer-update-checker", + "githubId": 41240800, + "isCore": false, + "lockstepped": false, + "type": "module", + "majorVersionMapping": { + "4": ["2", "3"], + "5": ["4"], + "6": ["5"] + } + }, + { + "github": "bringyourownideas/silverstripe-composer-security-checker", + "packagist": "bringyourownideas/silverstripe-composer-security-checker", + "githubId": 40122132, + "isCore": false, + "lockstepped": false, + "type": "module", + "majorVersionMapping": { + "4": ["2"] + } + }, + { + "github": "colymba/GridFieldBulkEditingTools", + "packagist": "colymba/gridfield-bulk-editing-tools", + "githubId": 5071848, + "isCore": false, + "lockstepped": false, + "type": "module", + "majorVersionMapping": { + "4": ["3"], + "5": ["4"], + "6": ["5"] + } + }, + { + "github": "silverstripe/cwp-agencyextensions", + "packagist": "cwp/agency-extensions", + "githubId": 113399978, + "isCore": false, + "lockstepped": false, + "type": "module", + "majorVersionMapping": { + "4": ["2"], + "5": ["3"], + "6": ["4"] + } + }, + { + "github": "silverstripe/cwp", + "packagist": "cwp/cwp", + "githubId": 113398740, + "isCore": false, + "lockstepped": false, + "type": "module", + "majorVersionMapping": { + "4": ["2"] + } + }, + { + "github": "silverstripe/cwp-core", + "packagist": "cwp/cwp-core", + "githubId": 113399915, + "isCore": false, + "lockstepped": false, + "type": "module", + "majorVersionMapping": { + "4": ["2"] + } + }, + { + "github": "silverstripe/cwp-pdfexport", + "packagist": "cwp/cwp-pdfexport", + "githubId": 118521425, + "isCore": false, + "lockstepped": false, + "type": "module", + "majorVersionMapping": { + "4": ["1"] + } + }, + { + "github": "silverstripe/cwp-search", + "packagist": "cwp/cwp-search", + "githubId": 116906416, + "isCore": false, + "lockstepped": false, + "type": "module", + "majorVersionMapping": { + "4": ["1"] + } + }, + { + "github": "silverstripe/cwp-starter-theme", + "packagist": "cwp/starter-theme", + "githubId": 109077240, + "isCore": false, + "lockstepped": false, + "type": "theme", + "majorVersionMapping": { + "4": ["3"], + "5": ["4"], + "6": ["5"] + } + }, + { + "github": "silverstripe/cwp-watea-theme", + "packagist": "cwp/watea-theme", + "githubId": 109077377, + "isCore": false, + "lockstepped": false, + "type": "theme", + "majorVersionMapping": { + "4": ["3"], + "5": ["4"], + "6": ["5"] + } + }, + { + "github": "silverstripe/developer-docs", + "packagist": "silverstripe/developer-docs", + "githubId": 510980223, + "isCore": true, + "lockstepped": false, + "type": "other", + "majorVersionMapping": { + "4": ["4"], + "5": ["5"], + "6": ["6"] + } + }, + { + "github": "silverstripe/silverstripe-elemental", + "packagist": "dnadesign/silverstripe-elemental", + "githubId": 23339883, + "isCore": false, + "lockstepped": false, + "type": "module", + "majorVersionMapping": { + "4": ["4"], + "5": ["5"], + "6": ["6"] + } + }, + { + "github": "dnadesign/silverstripe-elemental-subsites", + "packagist": "dnadesign/silverstripe-elemental-subsites", + "githubId": 96047352, + "isCore": false, + "lockstepped": false, + "type": "module", + "majorVersionMapping": { + "4": ["3"] + } + }, + { + "github": "dnadesign/silverstripe-elemental-userforms", + "packagist": "dnadesign/silverstripe-elemental-userforms", + "githubId": 96047938, + "isCore": false, + "lockstepped": false, + "type": "module", + "majorVersionMapping": { + "4": ["3"], + "5": ["4"], + "6": ["5"] + } + }, + { + "github": "hafriedlander/silverstripe-phockito", + "packagist": "hafriedlander/silverstripe-phockito", + "githubId": 2292890, + "isCore": false, + "lockstepped": false, + "type": "module", + "majorVersionMapping": { + "4": ["master"] + } + }, + { + "github": "lekoala/silverstripe-debugbar", + "packagist": "lekoala/silverstripe-debugbar", + "githubId": 60849433, + "isCore": false, + "lockstepped": false, + "type": "module", + "majorVersionMapping": { + "4": ["2"] + } + }, + { + "github": "silverstripe/silverstripe-admin", + "packagist": "silverstripe/admin", + "githubId": 84500508, + "isCore": true, + "lockstepped": true, + "type": "module", + "majorVersionMapping": { + "4": ["1"], + "5": ["2"], + "6": ["3"] + } + }, + { + "github": "silverstripe/silverstripe-akismet", + "packagist": "silverstripe/akismet", + "githubId": 32699251, + "isCore": false, + "lockstepped": false, + "type": "module", + "majorVersionMapping": { + "4": ["4"] + } + }, + { + "github": "silverstripe/silverstripe-asset-admin", + "packagist": "silverstripe/asset-admin", + "githubId": 42913926, + "isCore": true, + "lockstepped": true, + "type": "module", + "majorVersionMapping": { + "4": ["1"], + "5": ["2"], + "6": ["3"] + } + }, + { + "github": "silverstripe/silverstripe-assets", + "packagist": "silverstripe/assets", + "githubId": 85148184, + "isCore": true, + "lockstepped": true, + "type": "module", + "majorVersionMapping": { + "4": ["1"], + "5": ["2"], + "6": ["3"] + } + }, + { + "github": "silverstripe/silverstripe-auditor", + "packagist": "silverstripe/auditor", + "githubId": 47799024, + "isCore": false, + "lockstepped": false, + "type": "module", + "majorVersionMapping": { + "4": ["2"], + "5": ["3"], + "6": ["4"] + } + }, + { + "github": "silverstripe/silverstripe-blog", + "packagist": "silverstripe/blog", + "githubId": 1236910, + "isCore": false, + "lockstepped": false, + "type": "module", + "majorVersionMapping": { + "4": ["3"], + "5": ["4"], + "6": ["5"] + } + }, + { + "github": "silverstripe/silverstripe-campaign-admin", + "packagist": "silverstripe/campaign-admin", + "githubId": 85750633, + "isCore": true, + "lockstepped": true, + "type": "module", + "majorVersionMapping": { + "4": ["1"], + "5": ["2"], + "6": ["3"] + } + }, + { + "github": "silverstripe/silverstripe-ckan-registry", + "packagist": "silverstripe/ckan-registry", + "githubId": 159571764, + "isCore": false, + "lockstepped": false, + "type": "module", + "majorVersionMapping": { + "4": ["1"] + } + }, + { + "github": "silverstripe/silverstripe-cms", + "packagist": "silverstripe/cms", + "githubId": 1319183, + "isCore": true, + "lockstepped": true, + "type": "module", + "majorVersionMapping": { + "4": ["4"], + "5": ["5"], + "6": ["6"] + } + }, + { + "github": "silverstripe/comment-notifications", + "packagist": "silverstripe/comment-notifications", + "githubId": 32947509, + "isCore": false, + "lockstepped": false, + "type": "module", + "majorVersionMapping": { + "4": ["2"] + } + }, + { + "github": "silverstripe/silverstripe-comments", + "packagist": "silverstripe/comments", + "githubId": 1157974, + "isCore": false, + "lockstepped": false, + "type": "module", + "majorVersionMapping": { + "4": ["3"] + } + }, + { + "github": "silverstripe/silverstripe-config", + "packagist": "silverstripe/config", + "githubId": 66067831, + "isCore": true, + "lockstepped": false, + "type": "module", + "majorVersionMapping": { + "4": ["1"], + "5": ["2"], + "6": ["3"] + } + }, + { + "github": "silverstripe/silverstripe-content-widget", + "packagist": "silverstripe/content-widget", + "githubId": 34094648, + "isCore": false, + "lockstepped": false, + "type": "module", + "majorVersionMapping": { + "4": ["2"] + } + }, + { + "github": "silverstripe/silverstripe-contentreview", + "packagist": "silverstripe/contentreview", + "githubId": 2370478, + "isCore": false, + "lockstepped": false, + "type": "module", + "majorVersionMapping": { + "4": ["4"], + "5": ["5"], + "6": ["6"] + } + }, + { + "github": "silverstripe/silverstripe-crontask", + "packagist": "silverstripe/crontask", + "githubId": 12394679, + "isCore": false, + "lockstepped": false, + "type": "module", + "majorVersionMapping": { + "4": ["2"] + } + }, + { + "github": "silverstripe/silverstripe-documentconverter", + "packagist": "silverstripe/documentconverter", + "githubId": 113400338, + "isCore": false, + "lockstepped": false, + "type": "module", + "majorVersionMapping": { + "4": ["2"], + "5": ["3"], + "6": ["4"] + } + }, + { + "github": "silverstripe/silverstripe-elemental-bannerblock", + "packagist": "silverstripe/elemental-bannerblock", + "githubId": 136992112, + "isCore": false, + "lockstepped": false, + "type": "module", + "majorVersionMapping": { + "4": ["2"], + "5": ["3"], + "6": ["4"] + } + }, + { + "github": "silverstripe/silverstripe-elemental-fileblock", + "packagist": "silverstripe/elemental-fileblock", + "githubId": 136990365, + "isCore": false, + "lockstepped": false, + "type": "module", + "majorVersionMapping": { + "4": ["2"], + "5": ["3"], + "6": ["4"] + } + }, + { + "github": "silverstripe/silverstripe-environmentcheck", + "packagist": "silverstripe/environmentcheck", + "githubId": 3143218, + "isCore": false, + "lockstepped": false, + "type": "module", + "majorVersionMapping": { + "4": ["2"], + "5": ["3"], + "6": ["4"] + } + }, + { + "github": "silverstripe/silverstripe-errorpage", + "packagist": "silverstripe/errorpage", + "githubId": 94210313, + "isCore": true, + "lockstepped": true, + "type": "module", + "majorVersionMapping": { + "4": ["1"], + "5": ["2"], + "6": ["3"] + } + }, + { + "github": "silverstripe/silverstripe-externallinks", + "packagist": "silverstripe/externallinks", + "githubId": 22708348, + "isCore": false, + "lockstepped": false, + "type": "module", + "majorVersionMapping": { + "4": ["2"], + "5": ["3"], + "6": ["4"] + } + }, + { + "github": "silverstripe/silverstripe-framework", + "packagist": "silverstripe/framework", + "githubId": 1318892, + "isCore": true, + "lockstepped": true, + "type": "module", + "majorVersionMapping": { + "4": ["4"], + "5": ["5"], + "6": ["6"] + } + }, + { + "github": "silverstripe/silverstripe-fulltextsearch", + "packagist": "silverstripe/fulltextsearch", + "githubId": 1673985, + "isCore": false, + "lockstepped": false, + "type": "module", + "majorVersionMapping": { + "4": ["3"] + } + }, + { + "github": "silverstripe/silverstripe-graphql", + "packagist": "silverstripe/graphql", + "githubId": 68341446, + "isCore": true, + "lockstepped": false, + "type": "module", + "majorVersionMapping": { + "4": ["3", "4"], + "5": ["5"], + "6": ["6"] + } + }, + { + "github": "silverstripe/silverstripe-graphql-devtools", + "packagist": "silverstripe/graphql-devtools", + "githubId": 78792258, + "isCore": false, + "lockstepped": false, + "type": "module", + "majorVersionMapping": { + "4": ["1"] + } + }, + { + "github": "silverstripe/silverstripe-gridfieldqueuedexport", + "packagist": "silverstripe/gridfieldqueuedexport", + "githubId": 59252430, + "isCore": false, + "lockstepped": false, + "type": "module", + "majorVersionMapping": { + "4": ["2"], + "5": ["3"], + "6": ["4"] + } + }, + { + "github": "silverstripe/silverstripe-html5", + "packagist": "silverstripe/html5", + "githubId": 8889228, + "isCore": false, + "lockstepped": false, + "type": "module", + "majorVersionMapping": { + "4": ["2"] + } + }, + { + "github": "silverstripe/silverstripe-hybridsessions", + "packagist": "silverstripe/hybridsessions", + "githubId": 22979135, + "isCore": false, + "lockstepped": false, + "type": "module", + "majorVersionMapping": { + "4": ["2"], + "5": ["3"], + "6": ["4"] + } + }, + { + "github": "silverstripe/silverstripe-iframe", + "packagist": "silverstripe/iframe", + "githubId": 4515744, + "isCore": false, + "lockstepped": false, + "type": "module", + "majorVersionMapping": { + "4": ["2"], + "5": ["3"], + "6": ["4"] + } + }, + { + "github": "silverstripe/silverstripe-installer", + "packagist": "silverstripe/installer", + "githubId": 1319402, + "isCore": true, + "lockstepped": true, + "type": "recipe", + "majorVersionMapping": { + "4": ["4"], + "5": ["5"], + "6": ["6"] + } + }, + { + "github": "silverstripe/silverstripe-ldap", + "packagist": "silverstripe/ldap", + "githubId": 104963133, + "isCore": false, + "lockstepped": false, + "type": "module", + "majorVersionMapping": { + "4": ["1"], + "5": ["2"], + "6": ["3"] + } + }, + { + "github": "silverstripe/silverstripe-linkfield", + "packagist": "silverstripe/linkfield", + "githubId": 315191815, + "isCore": false, + "lockstepped": false, + "type": "module", + "majorVersionMapping": { + "5": ["4"], + "6": ["5"] + } + }, + { + "github": "silverstripe/silverstripe-lumberjack", + "packagist": "silverstripe/lumberjack", + "githubId": 30332001, + "isCore": false, + "lockstepped": false, + "type": "module", + "majorVersionMapping": { + "4": ["2"], + "5": ["3"], + "6": ["4"] + } + }, + { + "github": "silverstripe/silverstripe-mimevalidator", + "packagist": "silverstripe/mimevalidator", + "githubId": 22493606, + "isCore": false, + "lockstepped": false, + "type": "module", + "majorVersionMapping": { + "4": ["2"], + "5": ["3"], + "6": ["4"] + } + }, + { + "github": "silverstripe/silverstripe-postgresql", + "packagist": "silverstripe/postgresql", + "githubId": 1236928, + "isCore": false, + "lockstepped": false, + "type": "module", + "majorVersionMapping": { + "4": ["2"] + } + }, + { + "github": "silverstripe/silverstripe-realme", + "packagist": "silverstripe/realme", + "githubId": 46946194, + "isCore": false, + "lockstepped": false, + "type": "module", + "majorVersionMapping": { + "4": ["4"], + "5": ["5"], + "6": ["6"] + } + }, + { + "github": "silverstripe/silverstripe-session-manager", + "packagist": "silverstripe/session-manager", + "githubId": 128231892, + "isCore": true, + "lockstepped": false, + "type": "module", + "majorVersionMapping": { + "4": ["1"], + "5": ["2"], + "6": ["3"] + } + }, + { + "github": "silverstripe/recipe-authoring-tools", + "packagist": "silverstripe/recipe-authoring-tools", + "githubId": 120226694, + "isCore": false, + "lockstepped": true, + "type": "recipe", + "majorVersionMapping": { + "4": ["1"], + "5": ["2"], + "6": ["3"] + } + }, + { + "github": "silverstripe/recipe-blog", + "packagist": "silverstripe/recipe-blog", + "githubId": 119918895, + "isCore": false, + "lockstepped": true, + "type": "recipe", + "majorVersionMapping": { + "4": ["1"], + "5": ["2"], + "6": ["3"] + } + }, + { + "github": "silverstripe/recipe-ccl", + "packagist": "silverstripe/recipe-ccl", + "githubId": 411910754, + "isCore": false, + "lockstepped": false, + "type": "recipe", + "majorVersionMapping": { + "4": ["2"] + } + }, + { + "github": "silverstripe/recipe-cms", + "packagist": "silverstripe/recipe-cms", + "githubId": 96844605, + "isCore": true, + "lockstepped": true, + "type": "recipe", + "majorVersionMapping": { + "4": ["4"], + "5": ["5"], + "6": ["6"] + } + }, + { + "github": "silverstripe/recipe-collaboration", + "packagist": "silverstripe/recipe-collaboration", + "githubId": 119923751, + "isCore": false, + "lockstepped": true, + "type": "recipe", + "majorVersionMapping": { + "4": ["1"], + "5": ["2"], + "6": ["3"] + } + }, + { + "github": "silverstripe/recipe-content-blocks", + "packagist": "silverstripe/recipe-content-blocks", + "githubId": 120223778, + "isCore": false, + "lockstepped": true, + "type": "recipe", + "majorVersionMapping": { + "4": ["2"], + "5": ["3"], + "6": ["4"] + } + }, + { + "github": "silverstripe/recipe-core", + "packagist": "silverstripe/recipe-core", + "githubId": 96839278, + "isCore": true, + "lockstepped": true, + "type": "recipe", + "majorVersionMapping": { + "4": ["4"], + "5": ["5"], + "6": ["6"] + } + }, + { + "github": "silverstripe/recipe-form-building", + "packagist": "silverstripe/recipe-form-building", + "githubId": 120237364, + "isCore": false, + "lockstepped": true, + "type": "recipe", + "majorVersionMapping": { + "4": ["1"], + "5": ["2"], + "6": ["3"] + } + }, + { + "github": "silverstripe/recipe-plugin", + "packagist": "silverstripe/recipe-plugin", + "githubId": 67970412, + "isCore": true, + "lockstepped": false, + "type": "other", + "majorVersionMapping": { + "4": ["1"], + "5": ["2"], + "6": ["3"] + } + }, + { + "github": "silverstripe/recipe-reporting-tools", + "packagist": "silverstripe/recipe-reporting-tools", + "githubId": 120228554, + "isCore": false, + "lockstepped": true, + "type": "recipe", + "majorVersionMapping": { + "4": ["1"], + "5": ["2"], + "6": ["3"] + } + }, + { + "github": "silverstripe/recipe-services", + "packagist": "silverstripe/recipe-services", + "githubId": 120680662, + "isCore": false, + "lockstepped": true, + "type": "recipe", + "majorVersionMapping": { + "4": ["1"], + "5": ["2"], + "6": ["3"] + } + }, + { + "github": "silverstripe/recipe-solr-search", + "packagist": "silverstripe/recipe-solr-search", + "githubId": 411886231, + "isCore": false, + "lockstepped": true, + "type": "recipe", + "majorVersionMapping": { + "4": ["2"] + } + }, + { + "github": "silverstripe/recipe-kitchen-sink", + "packagist": "silverstripe/recipe-kitchen-sink", + "githubId": 397823445, + "isCore": false, + "lockstepped": true, + "type": "recipe", + "majorVersionMapping": { + "4": ["4"], + "5": ["5"], + "6": ["6"] + } + }, + { + "github": "silverstripe/silverstripe-registry", + "packagist": "silverstripe/registry", + "githubId": 8086664, + "isCore": false, + "lockstepped": false, + "type": "module", + "majorVersionMapping": { + "4": ["2"], + "5": ["3"], + "6": ["4"] + } + }, + { + "github": "silverstripe/silverstripe-reports", + "packagist": "silverstripe/reports", + "githubId": 7656757, + "isCore": true, + "lockstepped": true, + "type": "module", + "majorVersionMapping": { + "4": ["4"], + "5": ["5"], + "6": ["6"] + } + }, + { + "github": "silverstripe/silverstripe-restfulserver", + "packagist": "silverstripe/restfulserver", + "githubId": 4222524, + "isCore": false, + "lockstepped": false, + "type": "module", + "majorVersionMapping": { + "4": ["2"], + "5": ["3"], + "6": ["4"] + } + }, + { + "github": "silverstripe/silverstripe-securityreport", + "packagist": "silverstripe/securityreport", + "githubId": 19595761, + "isCore": false, + "lockstepped": false, + "type": "module", + "majorVersionMapping": { + "4": ["2"], + "5": ["3"], + "6": ["4"] + } + }, + { + "github": "silverstripe/silverstripe-segment-field", + "packagist": "silverstripe/segment-field", + "githubId": 40516528, + "isCore": false, + "lockstepped": false, + "type": "module", + "majorVersionMapping": { + "4": ["2"], + "5": ["3"], + "6": ["4"] + } + }, + { + "github": "silverstripe/silverstripe-sharedraftcontent", + "packagist": "silverstripe/sharedraftcontent", + "githubId": 35126267, + "isCore": false, + "lockstepped": false, + "type": "module", + "majorVersionMapping": { + "4": ["2"], + "5": ["3"], + "6": ["4"] + } + }, + { + "github": "silverstripe/silverstripe-siteconfig", + "packagist": "silverstripe/siteconfig", + "githubId": 22776092, + "isCore": true, + "lockstepped": true, + "type": "module", + "majorVersionMapping": { + "4": ["4"], + "5": ["5"], + "6": ["6"] + } + }, + { + "github": "silverstripe/silverstripe-sitewidecontent-report", + "packagist": "silverstripe/sitewidecontent-report", + "githubId": 43330250, + "isCore": false, + "lockstepped": false, + "type": "module", + "majorVersionMapping": { + "4": ["3"], + "5": ["4"], + "6": ["5"] + } + }, + { + "github": "silverstripe/silverstripe-spamprotection", + "packagist": "silverstripe/spamprotection", + "githubId": 1236936, + "isCore": false, + "lockstepped": false, + "type": "module", + "majorVersionMapping": { + "4": ["3"], + "5": ["4"], + "6": ["5"] + } + }, + { + "github": "silverstripe/silverstripe-spellcheck", + "packagist": "silverstripe/spellcheck", + "githubId": 22397728, + "isCore": false, + "lockstepped": false, + "type": "module", + "majorVersionMapping": { + "4": ["2"] + } + }, + { + "github": "silverstripe/silverstripe-sqlite3", + "packagist": "silverstripe/sqlite3", + "githubId": 1481572, + "isCore": false, + "lockstepped": false, + "type": "module", + "majorVersionMapping": { + "4": ["2"] + } + }, + { + "github": "silverstripe/sspak", + "packagist": "silverstripe/sspak", + "githubId": 9559572, + "isCore": false, + "lockstepped": false, + "type": "module", + "majorVersionMapping": { + "4": ["master"] + } + }, + { + "github": "silverstripe/silverstripe-staticpublishqueue", + "packagist": "silverstripe/staticpublishqueue", + "githubId": 9162434, + "isCore": false, + "lockstepped": false, + "type": "module", + "majorVersionMapping": { + "4": ["5"], + "5": ["6"], + "6": ["7"] + } + }, + { + "github": "silverstripe/silverstripe-subsites", + "packagist": "silverstripe/subsites", + "githubId": 1236940, + "isCore": false, + "lockstepped": false, + "type": "module", + "majorVersionMapping": { + "4": ["2"], + "5": ["3"], + "6": ["4"] + } + }, + { + "github": "silverstripe/silverstripe-tagfield", + "packagist": "silverstripe/tagfield", + "githubId": 1181344, + "isCore": false, + "lockstepped": false, + "type": "module", + "majorVersionMapping": { + "4": ["2"], + "5": ["3"], + "6": ["4"] + } + }, + { + "github": "silverstripe/silverstripe-taxonomy", + "packagist": "silverstripe/taxonomy", + "githubId": 8301510, + "isCore": false, + "lockstepped": false, + "type": "module", + "majorVersionMapping": { + "4": ["2"], + "5": ["3"], + "6": ["4"] + } + }, + { + "github": "silverstripe/silverstripe-textextraction", + "packagist": "silverstripe/textextraction", + "githubId": 7482455, + "isCore": false, + "lockstepped": false, + "type": "module", + "majorVersionMapping": { + "4": ["3"], + "5": ["4"], + "6": ["5"] + } + }, + { + "github": "silverstripe/silverstripe-userforms", + "packagist": "silverstripe/userforms", + "githubId": 1247754, + "isCore": false, + "lockstepped": false, + "type": "module", + "majorVersionMapping": { + "4": ["5"], + "5": ["6"], + "6": ["7"] + } + }, + { + "github": "silverstripe/vendor-plugin", + "packagist": "silverstripe/vendor-plugin", + "githubId": 104690866, + "isCore": true, + "lockstepped": false, + "type": "other", + "majorVersionMapping": { + "4": ["1"], + "5": ["2"], + "6": ["3"] + } + }, + { + "github": "silverstripe/silverstripe-versioned", + "packagist": "silverstripe/versioned", + "githubId": 85634633, + "isCore": true, + "lockstepped": true, + "type": "module", + "majorVersionMapping": { + "4": ["1"], + "5": ["2"], + "6": ["3"] + } + }, + { + "github": "silverstripe/silverstripe-versioned-admin", + "packagist": "silverstripe/versioned-admin", + "githubId": 124332817, + "isCore": true, + "lockstepped": true, + "type": "module", + "majorVersionMapping": { + "4": ["1"], + "5": ["2"], + "6": ["3"] + } + }, + { + "github": "silverstripe/silverstripe-versionfeed", + "packagist": "silverstripe/versionfeed", + "githubId": 6821471, + "isCore": false, + "lockstepped": false, + "type": "module", + "majorVersionMapping": { + "4": ["2"], + "5": ["3"], + "6": ["4"] + } + }, + { + "github": "silverstripe/silverstripe-widgets", + "packagist": "silverstripe/widgets", + "githubId": 4068399, + "isCore": false, + "lockstepped": false, + "type": "module", + "majorVersionMapping": { + "4": ["2"] + } + }, + { + "github": "silverstripe/silverstripe-simple", + "packagist": "silverstripe-themes/simple", + "githubId": 3712566, + "isCore": true, + "lockstepped": false, + "type": "theme", + "majorVersionMapping": { + "4": ["3"], + "5": ["3"], + "6": ["3"] + } + }, + { + "github": "symbiote/silverstripe-advancedworkflow", + "packagist": "symbiote/silverstripe-advancedworkflow", + "githubId": 981174, + "isCore": false, + "lockstepped": false, + "type": "module", + "majorVersionMapping": { + "4": ["5"], + "5": ["6"], + "6": ["7"] + } + }, + { + "github": "symbiote/silverstripe-gridfieldextensions", + "packagist": "symbiote/silverstripe-gridfieldextensions", + "githubId": 7373726, + "isCore": false, + "lockstepped": false, + "type": "module", + "majorVersionMapping": { + "4": ["3"], + "5": ["4"], + "6": ["5"] + } + }, + { + "github": "symbiote/silverstripe-multivaluefield", + "packagist": "symbiote/silverstripe-multivaluefield", + "githubId": 624044, + "isCore": false, + "lockstepped": false, + "type": "module", + "majorVersionMapping": { + "4": ["5"], + "5": ["6"], + "6": ["7"] + } + }, + { + "github": "symbiote/silverstripe-queuedjobs", + "packagist": "symbiote/silverstripe-queuedjobs", + "githubId": 660816, + "isCore": false, + "lockstepped": false, + "type": "module", + "majorVersionMapping": { + "4": ["4"], + "5": ["5"], + "6": ["6"] + } + }, + { + "github": "tractorcow-farm/silverstripe-fluent", + "packagist": "tractorcow/silverstripe-fluent", + "githubId": 10893201, + "isCore": false, + "lockstepped": false, + "type": "module", + "majorVersionMapping": { + "4": ["4"], + "5": ["7"], + "6": ["8"] + } + }, + { + "github": "undefinedoffset/sortablegridfield", + "packagist": "undefinedoffset/sortablegridfield", + "githubId": 4274219, + "isCore": false, + "lockstepped": false, + "type": "module", + "majorVersionMapping": { + "4": ["2"] + } + }, + { + "github": "silverstripe/silverstripe-mfa", + "packagist": "silverstripe/mfa", + "githubId": 172815373, + "isCore": false, + "lockstepped": false, + "type": "module", + "majorVersionMapping": { + "4": ["4"], + "5": ["5"], + "6": ["6"] + } + }, + { + "github": "silverstripe/silverstripe-totp-authenticator", + "packagist": "silverstripe/totp-authenticator", + "githubId": 179381590, + "isCore": false, + "lockstepped": false, + "type": "module", + "majorVersionMapping": { + "4": ["4"], + "5": ["5"], + "6": ["6"] + } + }, + { + "github": "silverstripe/silverstripe-webauthn-authenticator", + "packagist": "silverstripe/webauthn-authenticator", + "githubId": 176832496, + "isCore": false, + "lockstepped": false, + "type": "module", + "majorVersionMapping": { + "4": ["4"], + "5": ["5"], + "6": ["6"] + } + }, + { + "github": "silverstripe/silverstripe-login-forms", + "packagist": "silverstripe/login-forms", + "githubId": 155142697, + "isCore": false, + "lockstepped": false, + "type": "module", + "majorVersionMapping": { + "4": ["4"], + "5": ["5"], + "6": ["6"] + } + }, + { + "github": "silverstripe/silverstripe-security-extensions", + "packagist": "silverstripe/security-extensions", + "githubId": 190106499, + "isCore": false, + "lockstepped": false, + "type": "module", + "majorVersionMapping": { + "4": ["4"] + } + }, + { + "github": "silverstripe/silverstripe-dynamodb", + "packagist": "silverstripe/dynamodb", + "githubId": 38406904, + "isCore": false, + "lockstepped": false, + "type": "module", + "majorVersionMapping": { + "4": ["4"], + "5": ["5"], + "6": ["6"] + } + } + ], + "workflow": [ + { + "github": "silverstripe/gha-action-ci", + "githubId": 677151524, + "majorVersionMapping": { + "*": [] + } + }, + { + "github": "silverstripe/gha-auto-tag", + "githubId": 498115201, + "majorVersionMapping": { + "*": [] + } + }, + { + "github": "silverstripe/gha-ci", + "githubId": 498114968, + "majorVersionMapping": { + "*": [] + } + }, + { + "github": "silverstripe/gha-dispatch-ci", + "githubId": 614128526, + "majorVersionMapping": { + "*": [] + } + }, + { + "github": "silverstripe/gha-gauge-release", + "githubId": 677152725, + "majorVersionMapping": { + "*": [] + } + }, + { + "github": "silverstripe/gha-generate-matrix", + "githubId": 498115149, + "majorVersionMapping": { + "*": [] + } + }, + { + "github": "silverstripe/gha-issue", + "githubId": 652902116, + "majorVersionMapping": { + "*": [] + } + }, + { + "github": "silverstripe/gha-keepalive", + "githubId": 501033822, + "majorVersionMapping": { + "*": [] + } + }, + { + "github": "silverstripe/gha-merge-up", + "githubId": 665364839, + "majorVersionMapping": { + "*": [] + } + }, + { + "github": "silverstripe/gha-pull-request", + "githubId": 498115265, + "majorVersionMapping": { + "*": [] + } + }, + { + "github": "silverstripe/gha-run-tests", + "githubId": 498115111, + "majorVersionMapping": { + "*": [] + } + }, + { + "github": "silverstripe/gha-tag-release", + "githubId": 498115861, + "majorVersionMapping": { + "*": [] + } + }, + { + "github": "silverstripe/gha-trigger-ci", + "githubId": 679519830, + "majorVersionMapping": { + "*": [] + } + }, + { + "github": "silverstripe/gha-update-js", + "githubId": 498115920, + "majorVersionMapping": { + "*": [] + } + } + ], + "tooling": [ + { + "github": "silverstripe/recipe-testing", + "packagist": "silverstripe/recipe-testing", + "githubId": 125444094, + "majorVersionMapping": { + "4": ["2"], + "5": ["3"], + "6": ["4"] + } + }, + { + "github": "silverstripe/silverstripe-behat-extension", + "packagist": "silverstripe/behat-extension", + "githubId": 6235025, + "majorVersionMapping": { + "4": ["4"], + "5": ["5"], + "6": ["6"] + } + }, + { + "github": "silverstripe/MinkFacebookWebDriver", + "packagist": "silverstripe/mink-facebook-web-driver", + "githubId": 102808263, + "majorVersionMapping": { + "4": ["1"], + "5": ["2"], + "6": ["2"] + } + }, + { + "github": "silverstripe/cow", + "packagist": null, + "githubId": 45497366, + "majorVersionMapping": { + "*": [] + } + }, + { + "github": "silverstripe/rhino", + "packagist": null, + "githubId": 702760633, + "majorVersionMapping": { + "*": [] + } + }, + { + "github": "silverstripe/github-issue-search-client", + "packagist": null, + "githubId": 125294952, + "majorVersionMapping": { + "*": [] + } + }, + { + "github": "silverstripe/module-standardiser", + "packagist": null, + "githubId": 667697846, + "majorVersionMapping": { + "*": [] + } + }, + { + "github": "silverstripe/silverstripe-tx-translator", + "packagist": "silverstripe/tx-translator", + "githubId": 606951306, + "majorVersionMapping": { + "*": [] + } + }, + { + "github": "silverstripe/markdown-php-codesniffer", + "packagist": "silverstripe/markdown-php-codesniffer", + "githubId": 712195606, + "majorVersionMapping": { + "*": [] + } + }, + { + "github": "silverstripe/silverstripe-standards", + "packagist": "silverstripe/standards", + "githubId": 744766761, + "majorVersionMapping": { + "*": [] + } + }, + { + "github": "silverstripe/documentation-lint", + "packagist": "silverstripe/documentation-lint", + "githubId": 780708127, + "majorVersionMapping": { + "*": [] + } + }, + { + "github": "silverstripe/supported-modules", + "packagist": "silverstripe/supported-modules", + "githubId": 67956860, + "majorVersionMapping": { + "*": [] + } + } + ], + "misc": [ + { + "github": "silverstripe/eslint-config", + "packagist": null, + "githubId": 109643040, + "majorVersionMapping": { + "4": ["0"], + "5": ["1"], + "6": ["2"] + } + }, + { + "github": "silverstripe/webpack-config", + "packagist": null, + "githubId": 92692253, + "majorVersionMapping": { + "4": ["1"], + "5": ["2"], + "6": ["3"] + } + }, + { + "github": "silverstripe/.github", + "packagist": null, + "githubId": 599833538, + "majorVersionMapping": { + "*": [] + } + }, + { + "github": "silverstripe/api.silverstripe.org", + "packagist": null, + "githubId": 8220553, + "majorVersionMapping": { + "*": [] + } + }, + { + "github": "silverstripe/doc.silverstripe.org", + "packagist": null, + "githubId": 5683499, + "majorVersionMapping": { + "*": [] + } + }, + { + "github": "silverstripe/silverstripe-userhelp-content", + "packagist": null, + "githubId": 10051696, + "majorVersionMapping": { + "4": ["4"], + "5": ["5"] + } + }, + { + "github": "silverstripe/demo.silverstripe.org", + "packagist": null, + "githubId": 10230329, + "majorVersionMapping": { + "*": [] + } + }, + { + "github": "silverstripe/silverstripe-frameworktest", + "packagist": null, + "githubId": 1296840, + "majorVersionMapping": { + "4": ["0.4"], + "5": ["1"], + "6": ["2"] + } + }, + { + "github": "silverstripe/silverstripe-module", + "packagist": "silverstripe-module/skeleton", + "githubId": 45713708, + "majorVersionMapping": { + "4": ["4"], + "5": ["5"] + } + } + ] +} diff --git a/src/BranchLogic.php b/src/BranchLogic.php new file mode 100644 index 0000000..66367e7 --- /dev/null +++ b/src/BranchLogic.php @@ -0,0 +1,247 @@ + preg_match('#^[0-9]+\.?[0-9]*$#', $branch)); + + // If there are no relevant branches for a repository, there's nothing to merge up. + if (empty($repoBranches)) { + return []; + } + + $onlyMajorBranches = array_filter($repoBranches, fn ($branch) => ctype_digit((string) $branch)); + $majorDiff = static::getMajorDiff($repoMetaData, $onlyMajorBranches, $defaultBranch, $composerJson); + + $minorsWithStableTags = []; + foreach ($repoTags as $tag) { + if (!preg_match('#^([0-9]+)\.([0-9]+)\.([0-9]+)$#', $tag, $matches)) { + continue; + } + $major = $matches[1]; + $minor = $major. '.' . $matches[2]; + $minorsWithStableTags[$major][$minor] = true; + } + + $branches = []; + foreach ($repoBranches as $branch) { + // filter out majors that are too old - try getting the metadata CMS version first, + // since some repos have multiple branches for a given CMS major release line. + $cmsMajor = BranchLogic::getCmsMajor($repoMetaData, $branch); + if (!$cmsMajor) { + preg_match('#^([0-9]+)\.?[0-9]*$#', $branch, $matches); + $cmsMajor = $matches[1] + $majorDiff; + } + if ($cmsMajor < MetaData::LOWEST_SUPPORTED_CMS_MAJOR) { + continue; + } + // suffix a temporary .999 minor version to major branches so that it's sorted correctly later + if (preg_match('#^[0-9]+$#', $branch)) { + $branch .= '.999'; + } + $branches[] = $branch; + } + + // sort so that newest is first + usort($branches, 'version_compare'); + $branches = array_reverse($branches); + + // remove the temporary .999 + array_walk($branches, function(&$branch) { + $branch = preg_replace('#\.999$#', '', $branch); + }); + + // remove all branches except: + // - the latest major branch in each release line + // - the latest minor branch with a stable tag in each release line + // - any minor branches without stable tags with a higher minor version than the latest minor with a stable tag + $foundMinorInMajor = []; + $foundMinorBranchWithStableTag = []; + foreach ($branches as $i => $branch) { + // only remove minor branches, leave major branches in + if (!preg_match('#^([0-9]+)\.[0-9]+$#', $branch, $matches)) { + continue; + } + $major = $matches[1]; + if (isset($foundMinorBranchWithStableTag[$major]) && isset($foundMinorInMajor[$major])) { + unset($branches[$i]); + continue; + } + if (isset($minorsWithStableTags[$major][$branch])) { + $foundMinorBranchWithStableTag[$major] = true; + } + $foundMinorInMajor[$major] = true; + } + + // remove any branches less than or equal to DO_NOT_MERGE_UP_FROM_MAJOR + if (isset(MetaData::DO_NOT_MERGE_UP_FROM_MAJOR[$githubRepository])) { + $doNotMergeUpFromMajor = MetaData::DO_NOT_MERGE_UP_FROM_MAJOR[$githubRepository]; + $branches = array_filter($branches, function($branch) use ($doNotMergeUpFromMajor) { + return version_compare($branch, "$doNotMergeUpFromMajor.999999.999999", '>'); + }); + } + + // reverse the array so that oldest is first + return array_reverse($branches); + } + + private static function getCmsMajorFromBranch(array $repoMetaData, string $branch): string + { + $branchMajor = ''; + if (preg_match('#^[1-9]+$#', $branch)) { + $branchMajor = $branch; + } elseif (preg_match('#^([1-9]+)\.[0-9]+$#', $branch, $matches)) { + $branchMajor = $matches[1]; + } + foreach ($repoMetaData['majorVersionMapping'] ?? [] as $cmsMajor => $repoBranches) { + if (is_numeric($cmsMajor) && in_array($branchMajor, $repoBranches)) { + return $cmsMajor; + } + } + return ''; + } + + private static function getCmsMajorFromComposerJson(stdClass $composerJsonContent, bool $usePhpDepAsFallback): string + { + foreach (MetaData::getAllRepositoryMetaData() as $categoryData) { + foreach ($categoryData as $repoData) { + $composerName = $repoData['packagist'] ?? null; + if ($composerName === null || !isset($composerJsonContent->require->$composerName)) { + continue; + } + $parser = new VersionParser(); + $constraint = $parser->parseConstraints($composerJsonContent->require->$composerName); + $boundedVersion = explode('.', $constraint->getLowerBound()->getVersion()); + $composerVersionMajor = $boundedVersion[0]; + // If it's a non-numeric branch constraint or something unstable, don't use it + if ($composerVersionMajor === 0) { + continue; + } + foreach ($repoData['majorVersionMapping'] as $cmsMajor => $repoBranches) { + if (is_numeric($cmsMajor) && in_array($composerVersionMajor, $repoBranches)) { + return $cmsMajor; + } + } + } + } + // Fall back on PHP dependency if that's an option + if ($usePhpDepAsFallback && isset($composerJsonContent->require->php)) { + // Loop through in ascending order - the first result that matches is returned. + foreach (MetaData::PHP_VERSIONS_FOR_CMS_RELEASES as $cmsRelease => $phpVersions) { + // Ignore anything that's not a major release + if (!ctype_digit((string) $cmsRelease)) { + continue; + } + // Only look at the lowest-compatible PHP version of each major release, + // since there's some overlap between major releases + if (Semver::satisfies($phpVersions[0], $composerJsonContent->require->php)) { + return $cmsRelease; + } + } + } + return ''; + } + + /** + * Get the difference between the branch major and the CMS release major, e.g for silverstripe/admin CMS 5 => 5 - 2 = 3 + */ + private static function getMajorDiff(array $repoMetaData, array $onlyMajorBranches, string $defaultBranch, ?stdClass $composerJson): int + { + // work out default major + if (preg_match('#^([0-9]+)+\.?[0-9]*$#', $defaultBranch, $matches)) { + $defaultMajor = $matches[1]; + if (!in_array($defaultMajor, $onlyMajorBranches)) { + // Add default major to the end of the list, so it's checked last + $onlyMajorBranches[] = $defaultMajor; + } + } + + // Try to get diff from branch if we can + foreach ($onlyMajorBranches as $branch) { + $cmsMajor = (int) static::getCmsMajorFromBranch($repoMetaData, $branch); + if ($cmsMajor) { + return $cmsMajor - $branch; + } + } + + if ($composerJson !== null && isset($defaultMajor)) { + $cmsMajor = (int) static::getCmsMajorFromComposerJson($composerJson, true); + if ($cmsMajor) { + return $cmsMajor - $defaultMajor; + } + } + + // This is likely a maintenance-based respository such as silverstripe/eslint-config or silverstripe/gha-auto-tag + // Just treat them as though they're on the highest stable version. + if (isset($defaultMajor) && ($composerJson === null || array_key_exists('*', $repoMetaData['majorVersionMapping'] ?? []))) { + return MetaData::HIGHEST_STABLE_CMS_MAJOR - $defaultMajor; + } + + $repoName = static::getModuleName($repoMetaData, $composerJson) ?: 'this module'; + throw new RuntimeException("Could not work out what default CMS major version for $repoName"); + } + + private static function getModuleName(array $repoMetaData, ?stdClass $composerJson): string + { + if ($composerJson !== null && isset($composerJson->name)) { + return $composerJson->name; + } + if (isset($repoMetaData['packagist'])) { + return $repoMetaData['packagist']; + } + if (isset($repoMetaData['github'])) { + return $repoMetaData['github']; + } + return ''; + } +} diff --git a/src/MetaData.php b/src/MetaData.php new file mode 100644 index 0000000..a180199 --- /dev/null +++ b/src/MetaData.php @@ -0,0 +1,165 @@ + ['7.1', '7.2', '7.3', '7.4'], + '4.10' => ['7.3', '7.4', '8.0'], + '4.11' => ['7.4', '8.0', '8.1'], + '4' => ['7.4', '8.0', '8.1'], + '5.0' => ['8.1', '8.2'], + '5.1' => ['8.1', '8.2'], + '5.2' => ['8.1', '8.2', '8.3'], + '5' => ['8.1', '8.2', '8.3'], + '6' => ['8.1', '8.2', '8.3'], + ]; + + /** + * List of major branches to not merge up from + * + * Add repos in here where the repo was previously unsupported, where the repo has + * had gaps in its support history, or where we have had multiple supported modules + * for a given major release and want to omit one of those for merge-up purposes. + * + * Note these are actual major branches, not CMS major versions + */ + public const DO_NOT_MERGE_UP_FROM_MAJOR = [ + 'bringyourownideas/silverstripe-composer-update-checker' => '2', + 'silverstripe/silverstripe-graphql' => '3', + 'silverstripe/silverstripe-linkfield' => '3', + 'tractorcow-farm/silverstripe-fluent' => '4', + ]; + + /** + * List of repositories that should be outright skipped for merge-up purposes. + * Only list them if they're causing errors in the existing logic. + */ + public const SKIP_FOR_MERGE_UP = [ + 'silverstripe/cow', + ]; + + private static array $repositoryMetaData = []; + + /** + * Get metadata for a given repository, if we have any. + * + * @param string $gitHubReference The full GitHub reference for the repository + * e.g. `silverstripe/silverstripe-framework`. + * @param boolean $allowPartialMatch If no data is found for the full repository reference, + * check for repositories with the same name but a different organisation. + */ + public static function getMetaDataForRepository( + string $gitHubReference, + bool $allowPartialMatch = false + ): array { + $parts = explode('/', $gitHubReference); + if (count($parts) !== 2) { + throw new RuntimeException('$gitHubReference must be a valid org/repo reference.'); + } + $candidate = null; + foreach (self::getAllRepositoryMetaData() as $categoryData) { + foreach ($categoryData as $repoData) { + // Get data for the current repository + if ($repoData['github'] === $gitHubReference) { + // Exact match of org and repo name + return $repoData; + } elseif ($parts[1] === explode('/', $repoData['github'])[1]) { + // Partial match - repo name only + $candidate = $repoData; + } + } + } + if ($allowPartialMatch && $candidate !== null) { + return $candidate; + } + return []; + } + + /** + * Get metadata for a given repository based on the packagist name, if we have any. + * + * @param string $packagistName The full packagist reference for the repository + * e.g. `silverstripe/framework`. + */ + public static function getMetaDataByPackagistName(string $packagistName): array + { + if (!str_contains($packagistName, '/')) { + throw new RuntimeException('$packagistName must be a valid org/repo reference.'); + } + foreach (self::getAllRepositoryMetaData() as $categoryData) { + foreach ($categoryData as $repoData) { + // Get data for the packagist item + if (isset($repoData['packagist']) && $repoData['packagist'] === $packagistName) { + // Exact match of org and repo name + return $repoData; + } + } + } + return []; + } + + /** + * Get metadata for repositories that are released in lock-step with Silverstripe CMS minor releases. + */ + public static function getMetaDataForLocksteppedRepos(): array + { + $repos = []; + foreach (self::getAllRepositoryMetaData() as $category => $categoryData) { + // Skip anything that can't be lockstepped + if ($category !== self::CATEGORY_SUPPORTED) { + continue; + } + // Find lockstepped repos + foreach ($categoryData as $repoData) { + if (isset($repoData['lockstepped']) && $repoData['lockstepped'] && !empty($repoData['packagist'])) { + $repos[$repoData['packagist']] = $repoData['majorVersionMapping']; + } + } + } + return $repos; + } + + /** + * Get all metadata about all repositories we have information about + */ + public static function getAllRepositoryMetaData(): array + { + if (empty(self::$repositoryMetaData)) { + $rawJson = file_get_contents(__DIR__ . '/../repositories.json'); + $decodedJson = json_decode($rawJson, true); + if ($decodedJson === null) { + throw new RuntimeException('Could not parse repositories.json data: ' . json_last_error_msg()); + } + self::$repositoryMetaData = $decodedJson; + } + return self::$repositoryMetaData; + } +} diff --git a/templates/LICENSE.md b/templates/LICENSE.md deleted file mode 100644 index 65f4b0c..0000000 --- a/templates/LICENSE.md +++ /dev/null @@ -1,29 +0,0 @@ -BSD 3-Clause License - -Copyright (c) 2006-2018, SilverStripe Limited - www.silverstripe.com -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -* Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -* Neither the name of the copyright holder nor the names of its - contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/tests/BranchLogicTest.php b/tests/BranchLogicTest.php new file mode 100644 index 0000000..04e200f --- /dev/null +++ b/tests/BranchLogicTest.php @@ -0,0 +1,846 @@ + [ + 'githubRepository' => 'some/module', + 'branch' => '', + 'composerJson' => null, + 'usePhpDepAsFallback' => false, + 'expected' => '', + ], + 'PR branch not used to find data' => [ + 'githubRepository' => 'silverstripe/silverstripe-framework', + 'branch' => 'pulls/5/mybugfix', + 'composerJson' => null, + 'usePhpDepAsFallback' => true, + 'expected' => '', + ], + 'lockstepped with matching major' => [ + 'githubRepository' => 'silverstripe/silverstripe-framework', + 'branch' => '5', + 'composerJson' => null, + 'usePhpDepAsFallback' => false, + 'expected' => '5', + ], + 'lockstepped with matching major, use minor branch' => [ + 'githubRepository' => 'silverstripe/silverstripe-framework', + 'branch' => '5.2', + 'composerJson' => null, + 'usePhpDepAsFallback' => false, + 'expected' => '5', + ], + 'lockstepped with different major' => [ + 'githubRepository' => 'silverstripe/silverstripe-admin', + 'branch' => '3', + 'composerJson' => null, + 'usePhpDepAsFallback' => false, + 'expected' => '6', + ], + 'non-lockstepped' => [ + 'githubRepository' => 'silverstripe/silverstripe-tagfield', + 'branch' => '3.9', + 'composerJson' => null, + 'usePhpDepAsFallback' => false, + 'expected' => '5', + ], + 'non-module repo' => [ + 'githubRepository' => 'silverstripe/webpack-config', + 'branch' => '3', + 'composerJson' => null, + 'usePhpDepAsFallback' => false, + 'expected' => '6', + ], + 'n.x-dev constraint' => [ + 'githubRepository' => 'some/module', + 'branch' => 'mybranch', + 'composerJson' => [ + 'require' => ['silverstripe/framework' => '5.x-dev'], + ], + 'usePhpDepAsFallback' => false, + 'expected' => '5', + ], + 'n.m.x-dev constraint' => [ + 'githubRepository' => 'some/module', + 'branch' => 'mybranch', + 'composerJson' => [ + 'require' => ['silverstripe/framework' => '5.0.x-dev'], + ], + 'usePhpDepAsFallback' => false, + 'expected' => '5', + ], + '^n constraint' => [ + 'githubRepository' => 'some/module', + 'branch' => 'mybranch', + 'composerJson' => [ + 'require' => ['silverstripe/framework' => '^5'], + ], + 'usePhpDepAsFallback' => false, + 'expected' => '5', + ], + 'x.y.z constraint' => [ + 'githubRepository' => 'some/module', + 'branch' => 'mybranch', + 'composerJson' => [ + 'require' => ['silverstripe/framework' => '5.1.2'], + ], + 'usePhpDepAsFallback' => false, + 'expected' => '5', + ], + 'result is actual cms major, not just the dep major' => [ + 'githubRepository' => 'some/module', + 'branch' => 'mybranch', + 'composerJson' => [ + 'require' => ['silverstripe/admin' => '^2'], + ], + 'usePhpDepAsFallback' => false, + 'expected' => '5', + ], + 'If branch matches, composerjson is ignored' => [ + 'githubRepository' => 'silverstripe/silverstripe-framework', + 'branch' => '5.2', + 'composerJson' => [ + 'require' => ['silverstripe/admin' => '1.x-dev'], + ], + 'usePhpDepAsFallback' => false, + 'expected' => '5', + ], + 'composerjson used even for known modules if needed' => [ + 'githubRepository' => 'silverstripe/silverstripe-framework', + 'branch' => 'main', + 'composerJson' => [ + 'require' => ['silverstripe/admin' => '1.x-dev'], + ], + 'usePhpDepAsFallback' => false, + 'expected' => '4', + ], + 'composer plugins are valid deps' => [ + 'githubRepository' => 'some/module', + 'branch' => 'mybranch', + 'composerJson' => [ + 'require' => ['silverstripe/vendor-plugin' => '^1'], + ], + 'usePhpDepAsFallback' => false, + 'expected' => '4', + ], + 'branch is ignored when we lack metadata' => [ + 'githubRepository' => 'some/module', + 'branch' => '3', + 'composerJson' => [ + 'require' => ['silverstripe/framework' => '^5'], + ], + 'usePhpDepAsFallback' => false, + 'expected' => '5', + ], + 'framework takes presedence over composer plugins' => [ + 'githubRepository' => 'some/module', + 'branch' => '3', + 'composerJson' => [ + 'require' => [ + 'silverstripe/vendor-plugin' => '^1', + 'silverstripe/recipe-plugin' => '^1', + 'silverstripe/framework' => '^6' + ], + ], + 'usePhpDepAsFallback' => false, + 'expected' => '6', + ], + 'PHP only used if explicitly asked for' => [ + 'githubRepository' => 'some/module', + 'branch' => 'mybranch', + 'composerJson' => [ + 'require' => [ + 'php' => '^8.1', + 'unknown/dependency' => '^1' + ], + ], + 'usePhpDepAsFallback' => false, + 'expected' => '', + ], + 'PHP matches minimum allowed cms4' => [ + 'githubRepository' => 'some/module', + 'branch' => 'mybranch', + 'composerJson' => [ + 'require' => [ + 'php' => '^7.4', + 'unknown/dependency' => '^1' + ], + ], + 'usePhpDepAsFallback' => true, + 'expected' => '4', + ], + 'PHP matches minimum allowed cms5' => [ + 'githubRepository' => 'some/module', + 'branch' => 'mybranch', + 'composerJson' => [ + 'require' => [ + 'php' => '^8.1', + 'unknown/dependency' => '^1' + ], + ], + 'usePhpDepAsFallback' => true, + 'expected' => '5', + ], + 'PHP doesnt have to be exactly the same as installer constraint' => [ + 'githubRepository' => 'some/module', + 'branch' => 'mybranch', + 'composerJson' => [ + 'require' => [ + 'php' => '^8.0', + 'unknown/dependency' => '^1' + ], + ], + 'usePhpDepAsFallback' => true, + 'expected' => '5', + ], + 'tried everything, no match' => [ + 'githubRepository' => 'some/module', + 'branch' => '3', + 'composerJson' => [ + 'require' => [ + 'php' => '^5.4', + 'silverstripe/framework' => '^2', + ], + ], + 'usePhpDepAsFallback' => true, + 'expected' => '', + ], + ]; + } + + /** + * @dataProvider provideGetCmsMajor + */ + public function testGetCmsMajor( + string $githubRepository, + string $branch, + ?array $composerJson, + bool $usePhpDepAsFallback, + string $expected + ): void { + if (is_array($composerJson)) { + // Convert array json into stdClass + $composerJson = json_decode(json_encode($composerJson)); + } + $repoMetaData = MetaData::getMetaDataForRepository($githubRepository); + $cmsMajor = BranchLogic::getCmsMajor($repoMetaData, $branch, $composerJson, $usePhpDepAsFallback); + $this->assertSame($expected, $cmsMajor); + } + + public function provideGetBranchesForMergeUp(): array + { + return [ + 'no branches' => [ + 'githubRepository' => 'lorem/ipsum', + 'defaultBranch' => '5', + 'repoTags' => [ + '5.1.0-beta1', + '5.0.9', + '4.13.11', + '4.12.11', + '4.11.11', + '4.10.11', + '3.7.4' + ], + 'repoBranches' => [], + 'composerJson' => [ + 'require' => [ + 'silverstripe/framework' => '^5.0' + ] + ], + 'expected' => [], + ], + 'no tags' => [ + 'githubRepository' => 'lorem/ipsum', + 'defaultBranch' => '5', + 'repoTags' => [], + 'repoBranches' => [ + '3', + '3.6', + '3.7', + '4', + '4.10', + '4.11', + '4.12', + '4.13', + '5', + '5.0', + '5.1', + '6' + ], + 'composerJson' => [ + 'require' => [ + 'silverstripe/framework' => '^5.0' + ] + ], + // Note that this would result in an exception in the merge-ups action itself. + 'expected' => ['4.10', '4.11', '4.12', '4.13', '4', '5.0', '5.1', '5', '6'], + ], + '5.1.0-beta1, CMS 6 branch detected on silverstripe/framework' => [ + 'githubRepository' => 'lorem/ipsum', + 'defaultBranch' => '5', + 'repoTags' => [ + '5.1.0-beta1', + '5.0.9', + '4.13.11', + '4.12.11', + '4.11.11', + '4.10.11', + '3.7.4' + ], + 'repoBranches' => [ + '3', + '3.6', + '3.7', + '4', + '4.10', + '4.11', + '4.12', + '4.13', + '5', + '5.0', + '5.1', + '6' + ], + 'composerJson' => [ + 'require' => [ + 'silverstripe/framework' => '^5.0' + ] + ], + 'expected' => ['4.13', '4', '5.0', '5.1', '5', '6'], + ], + '5.1.0 stable and match on silverstripe/cms' => [ + 'githubRepository' => 'lorem/ipsum', + 'defaultBranch' => '5', + 'repoTags' => [ + '5.1.0', + '5.0.9', + '4.13.11', + '4.12.11', + '4.11.11', + '4.10.11', + '3.7.4' + ], + 'repoBranches' => [ + '3', + '3.6', + '3.7', + '4', + '4.10', + '4.11', + '4.12', + '4.13', + '5', + '5.0', + '5.1' + ], + 'composerJson' => [ + 'require' => [ + 'silverstripe/cms' => '^5.1' + ] + ], + 'expected' => ['4.13', '4', '5.1', '5'], + ], + 'match on silverstripe/assets' => [ + 'githubRepository' => 'lorem/ipsum', + 'defaultBranch' => '5', + 'repoTags' => [ + '5.1.0', + '5.0.9', + '4.13.11', + '4.12.11', + '4.11.11', + '4.10.11' + ], + 'repoBranches' => [ + '4', + '4.10', + '4.11', + '4.12', + '4.13', + '5', + '5.0', + '5.1' + ], + 'composerJson' => [ + 'require' => [ + 'silverstripe/assets' => '^2.0' + ] + ], + 'expected' => ['4.13', '4', '5.1', '5'], + ], + 'match on silverstripe/mfa' => [ + 'githubRepository' => 'lorem/ipsum', + 'defaultBranch' => '5', + 'repoTags' => [ + '5.1.0', + '5.0.9', + '4.13.11' + ], + 'repoBranches' => [ + '4', + '4.12', + '4.13', + '5', + '5.0', + '5.1' + ], + 'composerJson' => [ + 'require' => [ + 'silverstripe/mfa' => '^5.0' + ] + ], + 'expected' => ['4.13', '4', '5.1', '5'], + ], + 'Missing `1` branch and match on php version in composer.json' => [ + 'githubRepository' => 'lorem/ipsum', + 'defaultBranch' => '2', + 'repoTags' => [ + '2.1.0-beta1', + '2.0.9', + '1.13.11', + '1.12.11', + '1.11.11', + '1.10.11' + ], + 'repoBranches' => [ + '1.10', + '1.11', + '1.12', + '1.13', + '2', + '2.0', + '2.1' + ], + 'composerJson' => [ + 'require' => [ + 'php' => '^8.1' + ] + ], + 'expected' => ['1.13', '2.0', '2.1', '2'], + ], + 'Two minor branches without stable tags in composer.json' => [ + 'githubRepository' => 'lorem/ipsum', + 'defaultBranch' => '2', + 'repoTags' => [ + '2.3.0-alpha1', + '2.2.0-beta1', + '2.1.0', + '2.0.9', + '1.13.11' + ], + 'repoBranches' => [ + '2', + '2.0', + '2.1', + '2.2', + '2.3', + '1', + '1.13' + ], + 'composerJson' => [ + 'require' => [ + 'php' => '^8.1' + ] + ], + 'expected' => ['1.13', '1', '2.1', '2.2', '2.3', '2'], + ], + 'Module where default branch has not been changed from CMS 4 and there is a new CMS 6 branch' => [ + 'githubRepository' => 'lorem/ipsum', + 'defaultBranch' => '5', // this repo has a '5' branch for CMS 4 and a '6' branch for CMS 5 + 'repoTags' => [ + '6.0.0', + '5.9.1', + '4.0.1' + ], + 'repoBranches' => [ + '7', + '6', + '6.0', + '5', + '5.9', + '5.8', + '5.7' + ], + 'composerJson' => [ + 'require' => [ + 'php' => '^7.4', + 'silverstripe/framework' => '^4.11' + ] + ], + 'expected' => ['5.9', '5', '6.0', '6', '7'], + ], + 'developer-docs' => [ + 'githubRepository' => 'silverstripe/developer-docs', + 'defaultBranch' => '5', + 'repoTags' => [ + '4.13.0', + '5.0.0' + ], + 'repoBranches' => [ + '5', + '5.0', + '4.13', + '4.12', + '4', + '3' + ], + 'composerJson' => [ + 'no-require' => new stdClass(), + ], + 'expected' => ['4.13', '4', '5.0', '5'], + ], + 'More than 6 branches is fine' => [ + 'githubRepository' => 'lorem/ipsum', + 'defaultBranch' => '5', + 'repoTags' => [ + '5.2.0-beta1', + '5.1.0-beta1', + '5.0.9', + '4.13.11', + '4.12.11', + '4.11.11', + '4.10.11', + '3.7.4' + ], + 'repoBranches' => [ + '3', + '3.6', + '3.7', + '4', + '4.10', + '4.11', + '4.12', + '4.13', + '5', + '5.0', + '5.1', + '5.2', + '6' + ], + 'composerJson' => [ + 'require' => [ + 'silverstripe/framework' => '^5.0' + ] + ], + 'expected' => ['4.13', '4', '5.0', '5.1', '5.2', '5', '6'], + ], + 'cwp-watea-theme' => [ + 'githubRepository' => 'lorem/ipsum', + 'defaultBranch' => '4', + 'repoTags' => [ + '4.0.0', + '5.0.9', + '3.2.0', + '3.1.0', + '3.0.0' + ], + 'repoBranches' => [ + '1', + '1.0', + '2', + '2.0', + '3', + '3.0', + '3.1', + '3.2', + '4', + '4.0' + ], + 'composerJson' => [ + 'require' => [ + 'cwp/starter-theme' => '^4' + ] + ], + 'expected' => ['3.2', '3', '4.0', '4'], + ], + 'gha-ci' => [ + 'githubRepository' => 'silverstripe/gha-ci', + 'defaultBranch' => '1', + 'repoTags' => [ + '1.4.0', + '1.3.0', + '1.2.0', + '1.1.0', + '1.0.0' + ], + 'repoBranches' => [ + '1', + '1.0', + '1.1', + '1.2', + '1.3', + '1.4' + ], + 'composerJson' => null, + 'expected' => ['1.4', '1'], + ], + 'gha-generate-matrix with composerjson' => [ + 'githubRepository' => 'silverstripe/gha-generate-matrix', + 'defaultBranch' => '1', + 'repoTags' => [ + '1.4.0', + '1.3.0', + '1.2.0', + '1.1.0', + '1.0.0' + ], + 'repoBranches' => [ + '1', + '1.0', + '1.1', + '1.2', + '1.3', + '1.4' + ], + 'composerJson' => [ + 'require' => [ + 'something/random' => '^4' + ] + ], + 'expected' => ['1.4', '1'], + ], + 'silverstripe-linkfield beta' => [ + 'githubRepository' => 'silverstripe/silverstripe-linkfield', + 'defaultBranch' => '4', + 'repoTags' => [ + '3.0.0-beta1', + '2.0.0', + '1.0.0' + ], + 'repoBranches' => [ + '1', + '2', + '3', + '4', + '4.0', + '5' + ], + 'composerJson' => [ + 'require' => [ + 'silverstripe/framework' => '^5' + ] + ], + 'expected' => ['4.0', '4', '5'], + ], + 'silverstripe-linkfield stable' => [ + 'githubRepository' => 'silverstripe/silverstripe-linkfield', + 'defaultBranch' => '4', + 'repoTags' => [ + '4.0.0', + '3.0.0', + '2.0.0', + '1.0.0' + ], + 'repoBranches' => [ + '1', + '2', + '3', + '3.0', + '3.1', + '3.999', + '4', + '4.0', + '5' + ], + 'composerJson' => [ + 'require' => [ + 'silverstripe/framework' => '^5' + ] + ], + 'expected' => ['4.0', '4', '5'], + ], + 'Incorrect default branch for supported module' => [ + 'githubRepository' => 'silverstripe/silverstripe-cms', + 'defaultBranch' => 'main', + 'repoTags' => [ + '5.1.0', + '5.0.9', + '4.13.11', + '4.12.11', + '4.11.11', + '4.10.11', + '3.7.4' + ], + 'repoBranches' => [ + '3', + '3.6', + '3.7', + '4', + '4.10', + '4.11', + '4.12', + '4.13', + '5', + '5.0', + '5.1' + ], + 'composerJson' => null, + 'expected' => ['4.13', '4', '5.1', '5'], + ], + 'Incorrect default branch for supported module' => [ + 'githubRepository' => 'silverstripe/silverstripe-cms', + 'defaultBranch' => 'main', + 'repoTags' => [ + '5.1.0', + '5.0.9', + '4.13.11', + '4.12.11', + '4.11.11', + '4.10.11', + '3.7.4' + ], + 'repoBranches' => [ + '3', + '3.6', + '3.7', + '4', + '4.10', + '4.11', + '4.12', + '4.13', + '5', + '5.0', + '5.1' + ], + 'composerJson' => null, + 'expected' => ['4.13', '4', '5.1', '5'], + ], + 'Fluent, which had weird gaps in its support' => [ + 'githubRepository' => 'tractorcow-farm/silverstripe-fluent', + 'defaultBranch' => '7', + 'repoTags' => [ + '7.0.1', + '7.1.0', + '6.0.5', + '5.0.4', + '5.1.21', + '4.7.4', + '4.8.6' + ], + 'repoBranches' => [ + '8', + '7', + '7.0', + '7.1', + '6', + '6.0', + '5', + '5.0', + '5.1', + '4', + '4.7', + '4.8' + ], + 'composerJson' => null, + 'expected' => ['6.0', '6', '7.1', '7', '8'], + ], + ]; + } + + /** + * @dataProvider provideGetBranchesForMergeUp + */ + public function testGetBranchesForMergeUp( + string $githubRepository, + string $defaultBranch, + array $repoTags, + array $repoBranches, + ?array $composerJson, + array $expected + ): void { + $repoMetaData = MetaData::getMetaDataForRepository($githubRepository); + if (is_array($composerJson)) { + // Convert array json into stdClass + $composerJson = json_decode(json_encode($composerJson)); + } + $branches = BranchLogic::getBranchesForMergeUp( + $githubRepository, + $repoMetaData, + $defaultBranch, + $repoTags, + $repoBranches, + $composerJson + ); + $this->assertSame($expected, $branches); + } + + public function provideGetBranchesForMergeUpExceptions(): array + { + return [ + 'Incorrect default branch for random module' => [ + 'githubRepository' => 'lorem/ipsum', + 'defaultBranch' => 'main', + 'repoTags' => [ + '5.1.0', + '5.0.9', + '4.13.11', + '4.12.11', + '4.11.11', + '4.10.11', + '3.7.4' + ], + 'repoBranches' => [ + '3', + '3.6', + '3.7', + '4', + '4.10', + '4.11', + '4.12', + '4.13', + '5', + '5.0', + '5.1' + ], + // Even though we know what CMS major to use, because the default branch + // is incorrect we can't get a good mapping for the rest of the branches + 'composerJson' => [ + 'require' => [ + 'silverstripe/cms' => '^5.1', + ] + ], + ], + ]; + } + + /** + * @dataProvider provideGetBranchesForMergeUpExceptions + */ + public function testGetBranchesForMergeUpExceptions( + string $githubRepository, + string $defaultBranch, + array $repoTags, + array $repoBranches, + ?array $composerJson = null, + ): void { + $repoMetaData = MetaData::getMetaDataForRepository($githubRepository); + if (is_array($composerJson)) { + // Convert array json into stdClass + $composerJson = json_decode(json_encode($composerJson)); + } + + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage("Could not work out what default CMS major version for this module"); + + BranchLogic::getBranchesForMergeUp( + $githubRepository, + $repoMetaData, + $defaultBranch, + $repoTags, + $repoBranches, + $composerJson + ); + } +} diff --git a/tests/MetaDataTest.php b/tests/MetaDataTest.php new file mode 100644 index 0000000..889f168 --- /dev/null +++ b/tests/MetaDataTest.php @@ -0,0 +1,231 @@ + [ + 'repoName' => 'org/repo', + 'allowPartialMatch' => true, + 'resultEmpty' => true, + ], + 'packagist ref doesnt match github ref no partial' => [ + 'repoName' => 'silverstripe/framework', + 'allowPartialMatch' => false, + 'resultEmpty' => true, + ], + 'packagist ref doesnt match github ref with partial' => [ + 'repoName' => 'silverstripe/framework', + 'allowPartialMatch' => true, + 'resultEmpty' => true, + ], + 'exact match' => [ + 'repoName' => 'silverstripe/silverstripe-framework', + 'allowPartialMatch' => false, + 'resultEmpty' => false, + ], + 'fork mismatch' => [ + 'repoName' => 'creative-commoners/silverstripe-framework', + 'allowPartialMatch' => false, + 'resultEmpty' => true, + ], + 'fork match' => [ + 'repoName' => 'creative-commoners/silverstripe-framework', + 'allowPartialMatch' => true, + 'resultEmpty' => false, + ], + 'gha match' => [ + 'repoName' => 'silverstripe/gha-generate-matrix', + 'allowPartialMatch' => false, + 'resultEmpty' => false, + ], + ]; + } + + /** + * @dataProvider provideGetMetaDataForRepository + */ + public function testGetMetaDataForRepository(string $repoName, bool $allowPartialMatch, bool $resultEmpty): void + { + $repoData = MetaData::getMetaDataForRepository($repoName, $allowPartialMatch); + $this->assertSame($resultEmpty, empty($repoData)); + } + + public function testGetMetaDataForRepositoryInvalid(): void + { + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage('$gitHubReference must be a valid org/repo reference.'); + MetaData::getMetaDataForRepository(''); + } + + public function provideGetMetaDataByPackagistName(): array + { + return [ + 'missing repo' => [ + 'repoName' => 'org/repo', + 'resultEmpty' => true, + ], + 'packagist ref doesnt match github ref no partial' => [ + 'repoName' => 'silverstripe/silverstripe-framework', + 'resultEmpty' => true, + ], + 'packagist ref doesnt match github ref with partial' => [ + 'repoName' => 'silverstripe/silverstripe-framework', + 'resultEmpty' => true, + ], + 'exact match' => [ + 'repoName' => 'silverstripe/framework', + 'resultEmpty' => false, + ], + ]; + } + + /** + * @dataProvider provideGetMetaDataByPackagistName + */ + public function testGetMetaDataByPackagistName(string $repoName, bool $resultEmpty): void + { + $repoData = MetaData::getMetaDataByPackagistName($repoName); + $this->assertSame($resultEmpty, empty($repoData)); + } + + public function testGetMetaDataByPackagistNameInvalid(): void + { + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage('$packagistName must be a valid org/repo reference.'); + MetaData::getMetaDataByPackagistName(''); + } + + public function provideGetMetaDataForLocksteppedRepos(): array + { + return [ + 'module skeleton not lockstepped' => [ + 'packagistRef' => 'silverstripe-module/skeleton', + 'isLockstepped' => false, + ], + 'config not lockstepped' => [ + 'packagistRef' => 'silverstripe/config', + 'isLockstepped' => false, + ], + 'framework lockstepped' => [ + 'packagistRef' => 'silverstripe/framework', + 'isLockstepped' => true, + ], + 'kitchen sink lockstepped' => [ + 'packagistRef' => 'silverstripe/recipe-kitchen-sink', + 'isLockstepped' => true, + ], + ]; + } + + /** + * @dataProvider provideGetMetaDataForLocksteppedRepos + */ + public function testGetMetaDataForLocksteppedRepos(string $repoName, bool $isLockstepped): void + { + $lockstepped = MetaData::getMetaDataForLocksteppedRepos(); + + if ($isLockstepped) { + $this->assertArrayHasKey($repoName, $lockstepped); + $this->validateVersionMap($lockstepped[$repoName]); + } else { + $this->assertArrayNotHasKey($repoName, $lockstepped); + } + } + + public function testGetAllRepositoryMetaData(): void + { + // Validate data has correct categories + $validKeys = [ + MetaData::CATEGORY_SUPPORTED => [ + 'github', + 'packagist', + 'githubId', + 'isCore', + 'lockstepped', + 'type', + 'majorVersionMapping', + ], + MetaData::CATEGORY_WORKFLOW => [ + 'github', + 'githubId', + 'majorVersionMapping', + ], + MetaData::CATEGORY_TOOLING => [ + 'github', + 'packagist', + 'githubId', + 'majorVersionMapping', + ], + MetaData::CATEGORY_MISC => [ + 'github', + 'packagist', + 'githubId', + 'majorVersionMapping', + ], + ]; + $data = MetaData::getAllRepositoryMetaData(); + $this->assertSame(array_keys($validKeys), array_keys($data)); + + $githubRefs = []; + $packagistRefs = []; + $githubIds = []; + // Validate data schema + foreach ($data as $category => $repos) { + $this->assertIsArray($repos); + foreach ($repos as $repo) { + $this->validateSchema($repo, $validKeys[$category]); + if (isset($repo['github'])) { + $githubRefs[] = $repo['github']; + } + if (isset($repo['packagist'])) { + $packagistRefs[] = $repo['packagist']; + } + if (isset($repo['githubId'])) { + $githubIds[] = $repo['githubId']; + } + } + } + // Validate references are unique (no duplicated repositories) + $this->assertSame(array_unique($githubRefs), $githubRefs, 'GitHub references must be unique'); + $this->assertSame(array_unique($packagistRefs), $packagistRefs, 'Packagist references must be unique'); + $this->assertSame(array_unique($githubIds), $githubIds, 'GitHub IDs must be unique'); + } + + private function validateSchema(array $repo, array $validKeys): void + { + $this->assertSame($validKeys, array_keys($repo)); + in_array('github', $validKeys) && $this->assertStringContainsString('/', $repo['github']); + if (in_array('packagist', $validKeys)) { + if (is_string($repo['packagist'])) { + $this->assertStringContainsString('/', $repo['packagist']); + } else { + $this->assertNull($repo['packagist']); + } + } + in_array('githubId', $validKeys) && $this->assertIsInt($repo['githubId']); + in_array('isCore', $validKeys) && $this->assertIsBool($repo['isCore']); + in_array('lockstepped', $validKeys) && $this->assertIsBool($repo['lockstepped']); + in_array('type', $validKeys) && $this->assertContains($repo['type'], ['module', 'recipe', 'theme', 'other']); + in_array('majorVersionMapping', $validKeys) && $this->validateVersionMap($repo['majorVersionMapping']); + } + + private function validateVersionMap(array $versionMap): void + { + $this->assertNotEmpty($versionMap); + foreach ($versionMap as $cmsMajor => $branches) { + $this->assertIsArray($branches); + if ($cmsMajor !== '*') { + $this->assertTrue(ctype_digit((string)$cmsMajor)); + $this->assertNotEmpty($branches); + } + } + } +}