feat: add project versions (#110)

* refactor: allow version matrixes by projects

* feat: add initial version-matrix for mage-os

* feat: add project as optional input to action

* docs: document new input

* refactor: tighten types a bit

* chore: apply change requests from code review
This commit is contained in:
Vinai Kopp
2023-09-06 22:08:57 +02:00
committed by GitHub
parent 28643a7156
commit f7f0504691
27 changed files with 331 additions and 67 deletions
+10 -4
View File
@@ -1,6 +1,6 @@
# Magento 2 Supported Versions
A Github Action that computes the currently supported Github Actions Matrix for Magento 2 Versions
A GitHub Action that computes the currently supported GitHub Actions Matrix for Magento 2 Versions
All data comes from:
@@ -11,10 +11,11 @@ All data comes from:
See the [action.yml](./action.yml)
| Input | Description | Required | Default |
| --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------- | ----------- |
| Input | Description | Required | Default |
|-----------------| ------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------- |-----------------------|
| kind | The "kind" of support you're targeting for your package. Allowed values are `currently-supported`, `latest`, `custom`, `nightly` and `all` | false | 'currently-supported' |
| custom_versions | The versions you want to support, as a comma-separated string, i.e. 'magento/project-community-edition:2.3.7-p3, magento/project-community-edition:2.4.2-p2' | false | '' |
| project | The project to return the supported versions for. Allowed values are `mage-os` and `magento-open-source` | false | 'magento-open-source' |
| custom_versions | The versions you want to support, as a comma-separated string, i.e. 'magento/project-community-edition:2.3.7-p3, magento/project-community-edition:2.4.2-p2' | false | '' |
## Kinds
- `currently-supported` - The currently supported Magento Open Source versions by Adobe.
@@ -22,6 +23,11 @@ See the [action.yml](./action.yml)
- `custom` - A custom subset of the versions, as specified by you. Requires `custom_versions` sibling key.
- `nightly` - The nightly version of Magento (only available via `https://upstream-nightly.mage-os.org`)
- `all` - All versions of Magento (including patched/unpatched versions).
## Projects
- `mage-os`
- `magento-open-source` (default)
## Usage
```yml
+7 -2
View File
@@ -1,5 +1,5 @@
name: "Compute Supported Magento 2 Versions"
author: "Graycore"
name: "Compute Supported Mage-OS and Magento 2 Versions"
author: "Mage-OS"
description: "A Github Action that computes the Github Actions matrix for the chosen versions of Magento 2"
inputs:
@@ -7,6 +7,11 @@ inputs:
required: false
description: "The kind of versions you want to return. Allowed values are `currently-supported`, `latest`, `custom`, `nightly` and `all`"
default: "currently-supported"
project:
required: false
description: "The project to return the supported versions for. Allowed values are `mage-os` and `magento-open-source`"
# The default value is what it is to keep backward compatibility
default: "magento-open-source"
custom_versions:
required: false
description: "The specific custom versions of Magento that you want to use. Only applies when `kind` is `custom`"
+5 -1
View File
@@ -1,16 +1,20 @@
import * as core from '@actions/core';
import { validateKind } from './kind/validate-kinds';
import { getMatrixForKind } from './matrix/get-matrix-for-kind';
import { validateProject } from "./project/validate-projects";
export async function run(): Promise<void> {
try {
const kind = core.getInput("kind");
const customVersions = core.getInput("custom_versions");
const project = core.getInput("project");
validateProject(<any>project)
validateKind(<any>kind, customVersions ? customVersions.split(',') : undefined);
core.setOutput('matrix', getMatrixForKind(kind, customVersions));
core.setOutput('matrix', getMatrixForKind(kind, project, customVersions));
}
catch (error) {
core.setFailed(error.message);
@@ -1,14 +1,15 @@
import { getCurrentlySupportedVersions } from "./get-currently-supported";
import { Project } from "../project/projects";
describe('getCurrentlySupportedVersions for magento-open-source', () => {
const project: Project = "magento-open-source";
describe('getCurrentlySupportedVersions', () => {
it('should say that v2.4.0 is not supported in 2025', () => {
const date: Date = new Date('2025-01-01T00:00:00Z');
expect(getCurrentlySupportedVersions(date)).not.toContain('magento/project-community-edition:2.4.0');
expect(getCurrentlySupportedVersions(project, date)).not.toContain('magento/project-community-edition:2.4.0');
});
test.each([
//TODO: add a release-date so that past dates do not incur non-contemporaneous
// versions.
['2023-01-01T00:00:00Z', 'First day of 2023', [
'magento/project-community-edition:2.4.4-p2',
'magento/project-community-edition:2.4.5-p1',
@@ -49,7 +50,31 @@ describe('getCurrentlySupportedVersions', () => {
'supportedVersions for %s',
(date, description ,result) => {
expect(
getCurrentlySupportedVersions(new Date(date))
getCurrentlySupportedVersions(project, new Date(date))
).toEqual(result);
}
);
})
describe('getCurrentlySupportedVersions for mage-os', () => {
const project: Project = "mage-os";
it('should say that v1.0.0 is not supported in 2027', () => {
const date: Date = new Date('2027-01-01T00:00:00Z');
expect(getCurrentlySupportedVersions(project, date)).not.toContain('mage-os/project-community-edition:1.0.0');
});
test.each([
['2023-01-01T00:00:00Z', 'First day of 2023', [
]],
['2024-01-01T00:00:00Z', 'First day of 2024', [
'mage-os/project-community-edition:1.0.0',
]],
])(
'supportedVersions for %s',
(date, description ,result) => {
expect(
getCurrentlySupportedVersions(project, new Date(date))
).toEqual(result);
}
);
@@ -1,11 +1,13 @@
import { MagentoMatrixVersion } from '../matrix/matrix-type';
import allVersions from '../versions/individual.json';
import { PackageMatrixVersion } from '../matrix/matrix-type';
import { getIndividualVersionsForProject } from "../versions/get-versions-for-project";
export const getCurrentlySupportedVersions = (date: Date): string[] =>
Object.entries(<Record<string,MagentoMatrixVersion>>allVersions)
.filter(([key, value]) => {
const dayAfterRelease = new Date(value.release);
dayAfterRelease.setDate(dayAfterRelease.getDate() + 1);
return date >= dayAfterRelease && new Date(value.eol) >= date;
})
.map(([key, value]) => key);
export const getCurrentlySupportedVersions = (project: string, date: Date): string[] => {
const allVersions = getIndividualVersionsForProject(project)
return Object.entries(<Record<string,PackageMatrixVersion>>allVersions)
.filter(([key, value]) => {
const dayAfterRelease = new Date(value.release);
dayAfterRelease.setDate(dayAfterRelease.getDate() + 1);
return date >= dayAfterRelease && new Date(value.eol) >= date;
})
.map(([key, value]) => key);
}
-3
View File
@@ -1,3 +0,0 @@
[
"magento/project-community-edition"
]
-3
View File
@@ -1,3 +0,0 @@
[
"magento/project-community-edition:next"
]
@@ -0,0 +1,4 @@
{
"mage-os": ["mage-os/project-community-edition"],
"magento-open-source": ["magento/project-community-edition"]
}
@@ -0,0 +1,4 @@
{
"mage-os": ["mage-os/project-community-edition:next"],
"magento-open-source": ["magento/project-community-edition:next"]
}
@@ -1,49 +1,96 @@
import { getMatrixForKind } from "./get-matrix-for-kind";
describe('getMatrixForKind', () => {
describe('getMatrixForKind for mage-os', () => {
const project = "mage-os";
it('returns a matrix for `latest`', () => {
const result = getMatrixForKind("latest");
const result = getMatrixForKind("latest", project);
expect(result.magento).toBeDefined();
expect(result.include).toBeDefined();
});
it('returns a matrix for `currently-supported`', () => {
const result = getMatrixForKind("currently-supported");
const result = getMatrixForKind("currently-supported", project);
expect(result.magento).toBeDefined();
expect(result.include).toBeDefined();
});
it('returns a matrix for `all`', () => {
const result = getMatrixForKind("all");
const result = getMatrixForKind("all", project);
expect(result.magento).toBeDefined();
expect(result.include).toBeDefined();
});
it('returns a matrix for valid `custom`', () => {
const result = getMatrixForKind("custom", "magento/project-community-edition:2.3.7-p3");
const result = getMatrixForKind("custom", project, "mage-os/project-community-edition:1.0.0");
expect(result.magento).toBeDefined();
expect(result.include).toBeDefined();
});
it('returns a matrix for the next release when using `nightly`', () => {
const result = getMatrixForKind("nightly", "magento/project-community-edition:next");
expect(result.magento).toBeDefined();
expect(result.include).toBeDefined();
});
it('returns a matrix for valid multiple `custom`', () => {
const result = getMatrixForKind("custom", "magento/project-community-edition:2.3.7-p3,magento/project-community-edition:2.4.0");
const result = getMatrixForKind("nightly", project, "mage-os/project-community-edition:next");
expect(result.magento).toBeDefined();
expect(result.include).toBeDefined();
});
it('errors for invalid `custom``', () => {
expect(() => getMatrixForKind("custom")).toThrowError();
expect(() => getMatrixForKind("custom", project)).toThrowError();
});
})
describe('getMatrixForKind for magento-open-source', () => {
const project = "magento-open-source";
it('returns a matrix for `latest`', () => {
const result = getMatrixForKind("latest", project);
expect(result.magento).toBeDefined();
expect(result.include).toBeDefined();
});
it('returns a matrix for `currently-supported`', () => {
const result = getMatrixForKind("currently-supported", project);
expect(result.magento).toBeDefined();
expect(result.include).toBeDefined();
});
it('returns a matrix for `all`', () => {
const result = getMatrixForKind("all", project);
expect(result.magento).toBeDefined();
expect(result.include).toBeDefined();
});
it('returns a matrix for valid `custom`', () => {
const result = getMatrixForKind("custom", project, "magento/project-community-edition:2.3.7-p3");
expect(result.magento).toBeDefined();
expect(result.include).toBeDefined();
});
it('returns a matrix for the next release when using `nightly`', () => {
const result = getMatrixForKind("nightly", project, "magento/project-community-edition:next");
expect(result.magento).toBeDefined();
expect(result.include).toBeDefined();
});
it('returns a matrix for valid multiple `custom`', () => {
const result = getMatrixForKind("custom", project, "magento/project-community-edition:2.3.7-p3,magento/project-community-edition:2.4.0");
expect(result.magento).toBeDefined();
expect(result.include).toBeDefined();
});
it('errors for invalid `custom``', () => {
expect(() => getMatrixForKind("custom", project)).toThrowError();
});
})
@@ -1,24 +1,24 @@
import { getMatrixForVersions } from "./get-matrix-for-versions";
import latestJson from '../kind/latest.json';
import allVersions from '../versions/individual.json';
import nightly from '../kind/nightly.json';
import { getIndividualVersionsForProject } from "../versions/get-versions-for-project";
import latestJson from '../kind/special-versions/latest.json';
import nightlyJson from '../kind/special-versions/nightly.json';
import { amendMatrixForNext } from "../nightly/get-next-version";
import { getDayBefore } from '../nightly/get-day-before';
import { getCurrentlySupportedVersions } from "../kind/get-currently-supported";
export const getMatrixForKind = (kind: string, versions = "") => {
export const getMatrixForKind = (kind: string, project: string, versions = "") => {
switch(kind){
case 'latest':
return getMatrixForVersions(latestJson);
return getMatrixForVersions(project, latestJson[project]);
case 'currently-supported':
return getMatrixForVersions(getCurrentlySupportedVersions(new Date()));
return getMatrixForVersions(project, getCurrentlySupportedVersions(project, new Date()));
case 'nightly':
return amendMatrixForNext(getMatrixForVersions(nightly), 'https://upstream-mirror.mage-os.org', getDayBefore());
return amendMatrixForNext(getMatrixForVersions(project, nightlyJson[project]), 'https://upstream-mirror.mage-os.org', getDayBefore());
case 'all':
return getMatrixForVersions(Object.keys(allVersions));
return getMatrixForVersions(project, Object.keys(getIndividualVersionsForProject(project)));
case 'custom':
return getMatrixForVersions(versions.split(","))
return getMatrixForVersions(project, versions.split(","))
default:
throw new Error(`Unreachable kind: ${kind} discovered, please report to the maintainers.`);
}
@@ -1,16 +1,17 @@
import { GithubActionsMatrix, MagentoMatrixVersion } from "./matrix-type";
import compositeVersionJson from '../versions/composite.json';
import individualVersionJson from '../versions/individual.json';
const knownVersions : Record<string, MagentoMatrixVersion> = {...individualVersionJson, ...compositeVersionJson };
import { GithubActionsMatrix, PackageMatrixVersion } from "./matrix-type";
import { getIndividualVersionsForProject, getCompositeVersionsForProject } from "../versions/get-versions-for-project";
/**
* Computes the Github Actions Matrix for given versions of Magento
* Computes the GitHub Actions Matrix for given versions of Magento
*/
export const getMatrixForVersions = (versions: string[]): GithubActionsMatrix => {
export const getMatrixForVersions = (project: string, versions: string[]): GithubActionsMatrix => {
const knownVersions : Record<string, PackageMatrixVersion> = {
...getIndividualVersionsForProject(project), ...getCompositeVersionsForProject(project)
}
return versions.reduce((acc, current): GithubActionsMatrix => {
if(knownVersions[current] === undefined){
throw new Error("Unknown version while computing matrix");
if (knownVersions[current] === undefined){
throw new Error(`Unknown "${current}" version while computing matrix`);
}
return {
+2 -2
View File
@@ -1,4 +1,4 @@
export interface MagentoMatrixVersion {
export interface PackageMatrixVersion {
magento: string,
php: string | number,
composer: string | number,
@@ -15,5 +15,5 @@ export interface MagentoMatrixVersion {
export interface GithubActionsMatrix {
magento: string[],
include: MagentoMatrixVersion[]
include: PackageMatrixVersion[]
}
@@ -2,11 +2,15 @@ import { getNextVersion } from "./get-next-version"
describe('getNextVersion', () => {
it('should get the next nightly version for MageOS', () => {
expect(getNextVersion('https://upstream-mirror.mage-os.org', new Date('2022-09-29T17:47:00')), ).toEqual('@alpha');
it('should get the next nightly version for Magento Open Source', () => {
expect(getNextVersion('https://upstream-nightly.mage-os.org', new Date('2022-09-29T17:47:00')), ).toEqual('@alpha');
});
it('should get the next nightly version for Mage-OS', () => {
expect(getNextVersion('https://nightly.mage-os.org', new Date('2024-09-29T17:47:00')), ).toEqual('@alpha');
});
it('should handle the first of the month correctly', () => {
expect(getNextVersion('https://upstream-mirror.mage-os.org', new Date('2022-01-01T17:47:00')), ).toEqual('@alpha');
expect(getNextVersion('https://upstream-nightly.mage-os.org', new Date('2022-01-01T17:47:00')), ).toEqual('@alpha');
});
})
@@ -1,19 +1,28 @@
import { GithubActionsMatrix } from "../matrix/matrix-type";
export type Repository = "https://upstream-mirror.mage-os.org" | "https://repo.magento.com";
const KNOWN_REPOSITORIES = {
"https://repo.mage-os.org": true,
"https://nightly.mage-os.org": true,
"https://upstream-mirror.mage-os.org": true,
"https://upstream-nightly.mage-os.org": true,
"https://repo.magento.com": true,
}
export type Repository = keyof typeof KNOWN_REPOSITORIES;
/**
* A placeholder value use to refer to the next version of Magento.
* This value is just a placeholder, there is no "next" version (as of authoring).
*/
export const nextVersionPlaceHolder = "magento/project-community-edition:next";
export const nextVersionPlaceHolder = "project-community-edition:next";
/**
* Get the next version of Magento, as determined by the repository.
*/
export const getNextVersion = (repository: Repository, date: Date) => {
switch(repository){
case "https://upstream-mirror.mage-os.org":
case "https://nightly.mage-os.org":
case "https://upstream-nightly.mage-os.org":
// See: https://github.com/mage-os/generate-mirror-repo-js/blob/bbbdf1708ea0bf8fc845aad8240d00f37632b4a7/src/release-branch-build-tools.js#L71
return "@alpha";
default:
@@ -30,13 +39,14 @@ export const computeNextPackage = (packageName: string, repository: Repository,
return replaceNextPlaceHolderWithVersion(packageName, getNextVersion(repository, date));
}
export const amendMatrixForNext = (matrix: GithubActionsMatrix, repository: Repository = "https://upstream-mirror.mage-os.org", date: Date = new Date()): GithubActionsMatrix => {
matrix.magento = matrix.magento.map((item) => item === nextVersionPlaceHolder ? computeNextPackage(nextVersionPlaceHolder, repository, date) : item);
export const amendMatrixForNext = (matrix: GithubActionsMatrix, repository: Repository, date: Date = new Date()): GithubActionsMatrix => {
const nextVersionRegExp = new RegExp(nextVersionPlaceHolder + '$');
matrix.magento = matrix.magento.map((item) => item.match(nextVersionRegExp) ? computeNextPackage(item, repository, date) : item);
matrix.include = matrix.include.map((item) => {
return item.magento === nextVersionPlaceHolder
return item.magento.match(nextVersionRegExp)
? {
...item,
magento: computeNextPackage(nextVersionPlaceHolder, repository, date),
magento: computeNextPackage(item.magento, repository, date),
}
: item;
});
@@ -0,0 +1,9 @@
/**
* Acceptable arguments for version `project`
*/
export const KNOWN_PROJECTS = {
"mage-os": true,
"magento-open-source": true,
}
export type Project = keyof typeof KNOWN_PROJECTS;
@@ -0,0 +1,12 @@
import { validateProject } from "./validate-projects";
describe('validateProject', () => {
it('returns `true` if its a valid project', () => {
expect(validateProject("magento-open-source")).toBe(true);
expect(validateProject("mage-os")).toBe(true);
});
it('throws a helpful exception if it is an invalid project', () => {
expect(() => validateProject(<any>"quark")).toThrowError();
})
})
@@ -0,0 +1,6 @@
import { isKnownProject } from './validations/is-known-project';
import { ProjectValidator } from "./validator";
export const validateProject: ProjectValidator = (project): boolean => {
return isKnownProject(project)
}
@@ -0,0 +1,13 @@
import {isKnownProject} from "./is-known-project";
import {Project} from "../projects";
describe('isKnownProject', () => {
it('returns `true` for known projects', () => {
expect(isKnownProject("mage-os")).toBe(true)
expect(isKnownProject("magento-open-source")).toBe(true)
});
it('throws a message if for unknown projects', () => {
expect(() => isKnownProject(<Project>"bingo")).toThrowError()
});
})
@@ -0,0 +1,11 @@
import { KNOWN_PROJECTS, Project } from '../projects';
export const isKnownProject = (project: Project): boolean => {
if (!(project in KNOWN_PROJECTS)) {
throw new Error(
`Invalid project provided, supported projects are: ${Object.keys(KNOWN_PROJECTS).join(', ')}`
)
}
return true;
}
@@ -0,0 +1,3 @@
import { Project } from "./projects";
export type ProjectValidator = (project: Project) => boolean;
@@ -0,0 +1,24 @@
import { getIndividualVersionsForProject, getCompositeVersionsForProject } from "./get-versions-for-project";
import {Project} from "../project/projects";
describe('getIndividialVersionsForProject', () => {
it('returns individual versions matrix for magento-open-source', () => {
expect(Object.keys(getIndividualVersionsForProject("magento-open-source")).length).toBeGreaterThan(0)
expect(Object.keys(getIndividualVersionsForProject("mage-os")).length).toBeGreaterThan(0)
})
it('throws error if no individual versions are specified for given project', () => {
expect(() => getIndividualVersionsForProject(<Project>"ahsoka")).toThrowError()
})
})
describe('getCompositeVersionsForProject', () => {
it('returns composite versions matrix for magento-open-source', () => {
expect(Object.keys(getCompositeVersionsForProject("magento-open-source")).length).toBeGreaterThan(0)
expect(Object.keys(getCompositeVersionsForProject("mage-os")).length).toBeGreaterThan(0)
})
it('throws error if no composite versions are specified for given project', () => {
expect(() => getCompositeVersionsForProject(<Project>"spock")).toThrowError()
})
})
@@ -0,0 +1,34 @@
import { validateProject } from "../project/validate-projects";
import { PackageMatrixVersion } from "../matrix/matrix-type";
const individual = {
'mage-os': require('./mage-os/individual.json'),
'magento-open-source': require('./magento-open-source/individual.json')
}
const composite = {
'mage-os': require('./mage-os/composite.json'),
'magento-open-source': require('./magento-open-source/composite.json')
}
export const getIndividualVersionsForProject = (project: string): Record<string, PackageMatrixVersion> => {
validateProject(<any>project)
if (individual[project] === undefined) {
throw new Error(
`Project "${project}" has no individual version specifications`
)
}
return individual[project]
}
export const getCompositeVersionsForProject = (project: string): Record<string, PackageMatrixVersion> => {
validateProject(<any>project)
if (composite[project] === undefined) {
throw new Error(
`Project "${project}" has no composite version specifications`
)
}
return composite[project]
}
@@ -0,0 +1,30 @@
{
"mage-os/project-community-edition": {
"magento": "mage-os/project-community-edition",
"php": 8.1,
"composer": "2.2.21",
"mysql": "mysql:8.0",
"elasticsearch": "elasticsearch:8.5.3",
"rabbitmq": "rabbitmq:3.9-management",
"redis": "redis:7.0",
"varnish": "varnish:7.3",
"nginx": "nginx:1.22",
"os": "ubuntu-latest",
"release": "2023-09-15T00:00:00+0000",
"eol": "2026-09-15T00:00:00+0000"
},
"mage-os/project-community-edition:next": {
"magento": "mage-os/project-community-edition:next",
"php": 8.2,
"composer": "2",
"mysql": "mysql:8.0",
"elasticsearch": "elasticsearch:8.5.3",
"rabbitmq": "rabbitmq:3.11-management",
"redis": "redis:7.0",
"varnish": "varnish:7.3",
"nginx": "nginx:1.22",
"os": "ubuntu-latest",
"release": "2023-09-15T00:00:00+0000",
"eol": "2026-09-15T00:00:00+0000"
}
}
@@ -0,0 +1,16 @@
{
"mage-os/project-community-edition:1.0.0": {
"magento": "mage-os/project-community-edition:1.0.0",
"php": 8.1,
"composer": "2.2.21",
"mysql": "mysql:8.0",
"elasticsearch": "elasticsearch:8.5.3",
"rabbitmq": "rabbitmq:3.9-management",
"redis": "redis:7.0",
"varnish": "varnish:7.3",
"nginx": "nginx:1.22",
"os": "ubuntu-latest",
"release": "2023-09-15T00:00:00+0000",
"eol": "2026-09-15T00:00:00+0000"
}
}