mirror of
https://github.com/graycoreio/github-actions-magento2.git
synced 2026-06-08 19:46:41 +00:00
Compare commits
98 Commits
v7.0.0-rc.0
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| a097371e37 | |||
| 5ee0768610 | |||
| 3c51e99538 | |||
| 32a5fd2bad | |||
| 91bd008e62 | |||
| 36953b919c | |||
| 83f9433da0 | |||
| a7d48f567e | |||
| 76eb9064ff | |||
| 147245e120 | |||
| 4df4b25e05 | |||
| fa8e597365 | |||
| 1ea5a10ef6 | |||
| 863444afbd | |||
| befe0807f7 | |||
| 1e63c019ed | |||
| ebfdeb0b73 | |||
| 8a0f197a13 | |||
| 0bf08ef692 | |||
| 35c1ace2bc | |||
| d07afbacd0 | |||
| b71bb8b4aa | |||
| e39dd46f9c | |||
| b98313e100 | |||
| 0c7d14d885 | |||
| 6d4ca8d669 | |||
| b790da1859 | |||
| e89f6ad2e0 | |||
| 8e82fcc893 | |||
| 83ef32c838 | |||
| de7c47f07d | |||
| a2e3e7758b | |||
| e6bb7be524 | |||
| 3e9f95ee56 | |||
| 9c56da774b | |||
| 8c4fefd979 | |||
| d1a31d260d | |||
| d37f001ab6 | |||
| c5221f0d68 | |||
| 1fcb3618c0 | |||
| 4fc491bc1a | |||
| 5df6c1a0bd | |||
| ff5f76339b | |||
| 0df8ac6e57 | |||
| ef06f4566b | |||
| 4c9a28930b | |||
| 22627e1000 | |||
| b0131f0fa0 | |||
| 23c77e10c8 | |||
| 5fb823da94 | |||
| 38423ddb9b | |||
| d7959941c6 | |||
| 74f1e3ec39 | |||
| d38c375b83 | |||
| dcbd219ac1 | |||
| 761188e82f | |||
| aa1b545010 | |||
| 45d1df0162 | |||
| baef64bc0a | |||
| 59f87b6b2e | |||
| 0cbc4297b1 | |||
| c78e635688 | |||
| 5c04c25fe8 | |||
| 8d00f8149a | |||
| 2d7238de14 | |||
| 44e7c34892 | |||
| c53607cca8 | |||
| a729f8b2fd | |||
| f6a7355bd9 | |||
| 307f527997 | |||
| 85b7909eb1 | |||
| 837f1da96b | |||
| d311df7966 | |||
| a7e327d44f | |||
| c786530c3e | |||
| 483ec3ac17 | |||
| a1c6246c78 | |||
| 87989bb250 | |||
| bdb9528f8c | |||
| 6a520d49fd | |||
| 212f9a8e86 | |||
| 0808fab9c3 | |||
| bbd830745f | |||
| e31f6f656a | |||
| 198bc1072a | |||
| 3c0a90f92b | |||
| c115395583 | |||
| 20cbf5d06a | |||
| ff6279a518 | |||
| b74bdcde41 | |||
| 1c312fe567 | |||
| 62d2aec976 | |||
| 771dd05439 | |||
| f8036173e1 | |||
| bbecc7f5f9 | |||
| db1267a94b | |||
| 90babb16bf | |||
| 93c0b480e2 |
@@ -0,0 +1,13 @@
|
||||
---
|
||||
name: grill-me
|
||||
description: Interview the user relentlessly about a plan or design until reaching shared understanding, resolving each branch of the decision tree. Use when user wants to stress-test a plan, get grilled on their design, or mentions "grill me".
|
||||
---
|
||||
|
||||
Interview me relentlessly about every aspect of this plan until
|
||||
we reach a shared understanding. Walk down each branch of the design
|
||||
tree resolving dependencies between decisions one by one.
|
||||
|
||||
If a question can be answered by exploring the codebase, explore
|
||||
the codebase instead.
|
||||
|
||||
For each question, provide your recommended answer.
|
||||
@@ -0,0 +1,112 @@
|
||||
---
|
||||
name: new-versions
|
||||
description: Check magento/magento2 for newly published tags and add any missing entries to supported-version/src/versions/magento-open-source/{individual,composite}.json using Adobe's system requirements docs. Use when user wants to refresh Magento Open Source version data, mentions "new versions", or asks to check for new Magento releases.
|
||||
---
|
||||
|
||||
Refresh the Magento Open Source version data in this repo by adding any tags that have shipped recently but are not yet recorded.
|
||||
|
||||
## 1. Find new tags
|
||||
|
||||
List tags from `magento/magento2` via the GitHub API:
|
||||
|
||||
```
|
||||
gh api -X GET repos/magento/magento2/tags --paginate -q '.[].name' | head -50
|
||||
```
|
||||
|
||||
Focus on tags from the last several weeks. Tags look like `2.4.8-p4`, `2.4.7-p9`, etc. Ignore preview/RC tags unless the user asks otherwise.
|
||||
|
||||
For each candidate tag, get its publish date (needed for the `release` field):
|
||||
|
||||
```
|
||||
gh api repos/magento/magento2/git/refs/tags/<tag> -q '.object.url' | xargs -I{} gh api {} -q '.tagger.date // .author.date'
|
||||
```
|
||||
|
||||
## 2. Diff against the JSON files
|
||||
|
||||
The two files to check:
|
||||
|
||||
- `supported-version/src/versions/magento-open-source/individual.json` — one entry per concrete tag, keyed `magento/project-community-edition:<tag>`
|
||||
- `supported-version/src/versions/magento-open-source/composite.json` — range entries keyed `magento/project-community-edition:>=X.Y.Z <X.Y.(Z+1)`, plus the rolling entries `magento/project-community-edition` and `magento/project-community-edition:next`
|
||||
|
||||
A tag is "missing" if the `magento/project-community-edition:<tag>` key is absent from `individual.json`.
|
||||
|
||||
## 3. Fetch system requirements
|
||||
|
||||
For the minor version that the missing tag belongs to (e.g. `2.4.8` for `2.4.8-p4`), pull Adobe's system requirements page:
|
||||
|
||||
- https://experienceleague.adobe.com/en/docs/commerce-operations/installation-guide/system-requirements
|
||||
|
||||
Use `WebFetch` and extract: PHP, Composer, MySQL/MariaDB, Elasticsearch/OpenSearch, RabbitMQ, Redis/Valkey, Varnish, Nginx, supported OS. If a component (e.g. `elasticsearch`) is no longer listed for that minor version, omit the field from the new entry — do not carry it over from the previous patch. Compare the new entry against the most recent prior patch in `individual.json` to sanity-check which fields should be present.
|
||||
|
||||
For each service field, pin the **latest currently-published tag within the line Adobe lists**, derived from Docker Hub — not whatever the prior patch happened to carry.
|
||||
|
||||
- Adobe lists a major+minor (e.g. "Elasticsearch 8.19"): use the highest published `8.19.x` tag.
|
||||
- Adobe lists only a major (e.g. "Elasticsearch 8"): use the highest published `8.y.z` across all `8.x` minors (today: `8.19.15`).
|
||||
- Adobe lists multiple majors/lines (e.g. "OpenSearch 2.19, 3"): pick the newest line (`3`).
|
||||
|
||||
Query Docker Hub for the latest patch:
|
||||
|
||||
```
|
||||
curl -s "https://hub.docker.com/v2/repositories/library/elasticsearch/tags?page_size=100&name=8.19" \
|
||||
| python3 -c "import json,sys; d=json.load(sys.stdin); tags=[t['name'] for t in d['results'] if t['name'].startswith('8.19.') and t['name'].split('.')[2].isdigit()]; print(max(tags, key=lambda t:[int(x) for x in t.split('.')]))"
|
||||
```
|
||||
|
||||
For OpenSearch swap `library/elasticsearch` → `opensearchproject/opensearch`. Services already using rolling minor tags (`redis:7.2`, `varnish:7.7`, `nginx:1.28`, `rabbitmq:4.1-management`) are already "latest of the line" and need no bump.
|
||||
|
||||
Also fetch Adobe's lifecycle policy page for the line's EOL date:
|
||||
|
||||
- https://experienceleague.adobe.com/en/docs/commerce-operations/release/planning/lifecycle-policy
|
||||
|
||||
## 4. Write the entries
|
||||
|
||||
### individual.json
|
||||
|
||||
Append the new patch entry preserving file ordering (group by minor version, ascending patch number). Schema:
|
||||
|
||||
```json
|
||||
"magento/project-community-edition:<tag>": {
|
||||
"magento": "magento/project-community-edition:<tag>",
|
||||
"php": <number>,
|
||||
"composer": "<string>",
|
||||
"mysql": "mysql:<ver>" | "mariadb:<ver>",
|
||||
"opensearch": "opensearchproject/opensearch:<ver>",
|
||||
"elasticsearch": "elasticsearch:<ver>",
|
||||
"rabbitmq": "rabbitmq:<ver>-management",
|
||||
"redis": "redis:<ver>",
|
||||
"valkey": "valkey/valkey:<ver>",
|
||||
"varnish": "varnish:<ver>",
|
||||
"nginx": "nginx:<ver>",
|
||||
"os": "ubuntu-latest",
|
||||
"release": "<ISO8601 from tag date>",
|
||||
"eol": "<ISO8601 — see EOL rules below>"
|
||||
}
|
||||
```
|
||||
|
||||
### EOL rules
|
||||
|
||||
- The newest patch on a line gets `eol` set to the line's EOL date from Adobe's lifecycle policy page.
|
||||
- When a newer patch on the same line releases, overwrite the previous patch's `eol` with the newer patch's `release` date. So when adding a new patch, also update the prior patch's `eol` accordingly.
|
||||
- Net effect: at any moment only the latest patch on a line carries the line's lifecycle EOL; every older patch's `eol` equals the release date of its successor.
|
||||
|
||||
### composite.json
|
||||
|
||||
The composite range entry for the affected minor (e.g. `magento/project-community-edition:>=2.4.8 <2.4.9`) should reflect the highest patch's stack. Update its fields to match the new entry if the new tag is now the highest in that minor.
|
||||
|
||||
The rolling entries `magento/project-community-edition` and `magento/project-community-edition:next` must always mirror the system requirements of the highest tag across all minors (i.e. the absolute newest patch you just added, if it is the newest overall). Update PHP, Composer, MySQL, OpenSearch, RabbitMQ, Valkey, Varnish, Nginx, OS, release, eol on both. The `magento` field on `:next` stays `magento/project-community-edition:@alpha`.
|
||||
|
||||
## 5. Verify
|
||||
|
||||
After edits:
|
||||
|
||||
```
|
||||
cd supported-version && npm test
|
||||
```
|
||||
|
||||
All tests must pass before declaring done.
|
||||
|
||||
## Notes
|
||||
|
||||
- Do not remove existing entries — only add or update.
|
||||
- Use the tag's actual publish timestamp from the GitHub API for `release`, not today's date.
|
||||
- If Adobe's docs are ambiguous for a given component, ask the user before guessing.
|
||||
- Preserve the file's existing key ordering and indentation (4 spaces).
|
||||
@@ -0,0 +1,23 @@
|
||||
name: Cache Magento Test
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
paths:
|
||||
- "cache-magento/**"
|
||||
- ".github/workflows/_internal-cache-magento.yaml"
|
||||
- "!**/*.md"
|
||||
pull_request:
|
||||
branches: [main]
|
||||
paths:
|
||||
- "cache-magento/**"
|
||||
- ".github/workflows/_internal-cache-magento.yaml"
|
||||
- "!**/*.md"
|
||||
|
||||
jobs:
|
||||
unit:
|
||||
if: "!startsWith(github.head_ref, 'release-please')"
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- run: bash cache-magento/test.sh
|
||||
@@ -0,0 +1,108 @@
|
||||
name: Check Store Test
|
||||
|
||||
on:
|
||||
workflow_dispatch: {}
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- ".github/workflows/_internal-check-store.yaml"
|
||||
- ".github/workflows/check-store.yaml"
|
||||
- "supported-version/**"
|
||||
- "get-magento-version/**"
|
||||
- "!**/*.md"
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- ".github/workflows/_internal-check-store.yaml"
|
||||
- ".github/workflows/check-store.yaml"
|
||||
- "supported-version/**"
|
||||
- "get-magento-version/**"
|
||||
- "!**/*.md"
|
||||
|
||||
jobs:
|
||||
compute_matrix:
|
||||
if: "!startsWith(github.head_ref, 'release-please')"
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
matrix: ${{ steps.supported-version.outputs.matrix }}
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: ./supported-version
|
||||
id: supported-version
|
||||
with:
|
||||
kind: currently-supported
|
||||
|
||||
prepare-fixture:
|
||||
needs: compute_matrix
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix: ${{ fromJSON(needs.compute_matrix.outputs.matrix) }}
|
||||
fail-fast: false
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- uses: ./setup-magento
|
||||
id: setup-magento
|
||||
with:
|
||||
php-version: ${{ matrix.php }}
|
||||
tools: composer:v${{ matrix.composer }}
|
||||
magento_version: ${{ matrix.magento }}
|
||||
mode: extension
|
||||
composer_auth: ${{ secrets.COMPOSER_AUTH }}
|
||||
|
||||
- run: composer update --no-install
|
||||
working-directory: ${{ steps.setup-magento.outputs.path }}
|
||||
env:
|
||||
COMPOSER_AUTH: ${{ secrets.COMPOSER_AUTH }}
|
||||
|
||||
- uses: ./cache-magento
|
||||
with:
|
||||
composer_cache_key: ${{ matrix.magento }}
|
||||
working-directory: ${{ steps.setup-magento.outputs.path }}
|
||||
stamp: true
|
||||
|
||||
- name: Inspect stamp cache contents
|
||||
if: always()
|
||||
working-directory: ${{ steps.setup-magento.outputs.path }}
|
||||
run: |
|
||||
echo "=== top-level vendor/magento ==="
|
||||
ls vendor/magento/ 2>/dev/null | head -30 || echo "(no vendor/magento)"
|
||||
echo
|
||||
echo "=== magento2-base presence ==="
|
||||
if [ -d vendor/magento/magento2-base ]; then
|
||||
echo "PRESENT — file count: $(find vendor/magento/magento2-base -type f | wc -l)"
|
||||
else
|
||||
echo "ABSENT (negation worked)"
|
||||
fi
|
||||
echo
|
||||
echo "=== installed.json mentions magento/magento2-base ==="
|
||||
grep -c '"name": "magento/magento2-base"' vendor/composer/installed.json 2>/dev/null || echo 0
|
||||
|
||||
- run: composer install
|
||||
working-directory: ${{ steps.setup-magento.outputs.path }}
|
||||
env:
|
||||
COMPOSER_AUTH: ${{ secrets.COMPOSER_AUTH }}
|
||||
|
||||
- uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: store-fixture-${{ matrix.version }}
|
||||
path: |
|
||||
${{ steps.setup-magento.outputs.path }}
|
||||
!${{ steps.setup-magento.outputs.path }}/vendor
|
||||
retention-days: 3
|
||||
|
||||
check-store:
|
||||
needs: [compute_matrix, prepare-fixture]
|
||||
strategy:
|
||||
matrix: ${{ fromJSON(needs.compute_matrix.outputs.matrix) }}
|
||||
fail-fast: false
|
||||
uses: ./.github/workflows/check-store.yaml
|
||||
with:
|
||||
store_artifact_name: store-fixture-${{ matrix.version }}
|
||||
path: "_ghamagento/"
|
||||
composer_cache_key: ${{ matrix.magento }}
|
||||
stamp: true
|
||||
secrets:
|
||||
composer_auth: ${{ secrets.COMPOSER_AUTH }}
|
||||
@@ -8,7 +8,7 @@ on:
|
||||
- "_test/demo-package/**"
|
||||
- ".github/workflows/_internal-coding-standard.yaml"
|
||||
- "coding-standard/**"
|
||||
- "!(**/*.md)"
|
||||
- "!**/*.md"
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
@@ -16,7 +16,7 @@ on:
|
||||
- "_test/demo-package/**"
|
||||
- ".github/workflows/_internal-coding-standard.yaml"
|
||||
- "coding-standard/**"
|
||||
- "!(**/*.md)"
|
||||
- "!**/*.md"
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
version:
|
||||
|
||||
@@ -8,14 +8,14 @@ on:
|
||||
paths:
|
||||
- ".github/workflows/_internal-get-composer-version.yaml"
|
||||
- "get-composer-version/**"
|
||||
- "!(**/*.md)"
|
||||
- "!**/*.md"
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- ".github/workflows/_internal-get-composer-version.yaml"
|
||||
- "get-composer-version/**"
|
||||
- "!(**/*.md)"
|
||||
- "!**/*.md"
|
||||
|
||||
jobs:
|
||||
get-composer-version:
|
||||
|
||||
@@ -7,14 +7,14 @@ on:
|
||||
paths:
|
||||
- ".github/workflows/_internal-get-magento-version.yaml"
|
||||
- "get-magento-version/**"
|
||||
- "!(**/*.md)"
|
||||
- "!**/*.md"
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- ".github/workflows/_internal-get-magento-version.yaml"
|
||||
- "get-magento-version/**"
|
||||
- "!(**/*.md)"
|
||||
- "!**/*.md"
|
||||
|
||||
jobs:
|
||||
get-magento-version:
|
||||
@@ -30,15 +30,43 @@ jobs:
|
||||
|
||||
- run: composer create-project --repository-url="https://mirror.mage-os.org" "magento/project-community-edition:2.4.5-p1" ../magento2 --no-install
|
||||
shell: bash
|
||||
name: Create Magento ${{ matrix.magento }} Project
|
||||
name: Create Magento ${{ matrix.magento }} Project
|
||||
env:
|
||||
COMPOSER_AUTH: ${{ secrets.COMPOSER_AUTH }}
|
||||
|
||||
- uses: ./get-magento-version
|
||||
id: magento-version
|
||||
with:
|
||||
working-directory: ../magento2
|
||||
|
||||
- name: Fail if key does not match
|
||||
- name: Fail if version does not match
|
||||
if: steps.magento-version.outputs.version != '"2.4.5-p1"'
|
||||
shell: bash
|
||||
run: echo "${{ steps.magento-version.outputs.version }}" && exit 1
|
||||
|
||||
|
||||
- name: Fail if project does not match
|
||||
if: steps.magento-version.outputs.project != 'magento/project-community-edition'
|
||||
shell: bash
|
||||
run: echo "${{ steps.magento-version.outputs.project }}" && exit 1
|
||||
|
||||
get-magento-version-extension:
|
||||
if: "!startsWith(github.head_ref, 'release-please')"
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Create mock extension composer.json
|
||||
shell: bash
|
||||
run: |
|
||||
mkdir -p /tmp/test-extension
|
||||
echo '{"name":"vendor/module","type":"magento2-module","require":{}}' > /tmp/test-extension/composer.json
|
||||
|
||||
- uses: ./get-magento-version
|
||||
id: ext-version
|
||||
with:
|
||||
working-directory: /tmp/test-extension
|
||||
|
||||
- name: Fail if project is not empty
|
||||
if: steps.ext-version.outputs.project != ''
|
||||
shell: bash
|
||||
run: echo "Expected empty project, got '${{ steps.ext-version.outputs.project }}'" && exit 1
|
||||
|
||||
@@ -10,7 +10,7 @@ on:
|
||||
- ".github/workflows/_internal-integration.yaml"
|
||||
- ".github/workflows/integration.yaml"
|
||||
- "supported-version/**"
|
||||
- "!(**/*.md)"
|
||||
- "!**/*.md"
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
@@ -19,7 +19,7 @@ on:
|
||||
- ".github/workflows/_internal-integration.yaml"
|
||||
- ".github/workflows/integration.yaml"
|
||||
- "supported-version/**"
|
||||
- "!(**/*.md)"
|
||||
- "!**/*.md"
|
||||
|
||||
jobs:
|
||||
compute_matrix:
|
||||
@@ -32,7 +32,6 @@ jobs:
|
||||
- uses: ./supported-version
|
||||
with:
|
||||
kind: currently-supported
|
||||
include_services: true
|
||||
id: supported-version
|
||||
integration-workflow:
|
||||
needs: compute_matrix
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
name: Sansec eComscan Security Scan
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- ".github/workflows/_internal-sansec-ecomscan.yaml"
|
||||
- "sansec-ecomscan/**"
|
||||
- "!**/*.md"
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- ".github/workflows/_internal-sansec-ecomscan.yaml"
|
||||
- "sansec-ecomscan/**"
|
||||
- "!**/*.md"
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
MAGENTO_COMPOSER_REPO: "https://mirror.mage-os.org/"
|
||||
|
||||
jobs:
|
||||
compute_matrix:
|
||||
if: "!startsWith(github.head_ref, 'release-please')"
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
matrix: ${{ steps.supported-version.outputs.matrix }}
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: ./supported-version
|
||||
with:
|
||||
kind: currently-supported
|
||||
id: supported-version
|
||||
|
||||
run-ecomscan:
|
||||
needs: compute_matrix
|
||||
strategy:
|
||||
matrix: ${{ fromJSON(needs.compute_matrix.outputs.matrix) }}
|
||||
fail-fast: false
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: read
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- uses: ./setup-magento
|
||||
id: setup-magento
|
||||
with:
|
||||
php-version: ${{ matrix.php }}
|
||||
tools: composer:v${{ matrix.composer }}
|
||||
mode: extension
|
||||
magento_repository: ${{ env.MAGENTO_COMPOSER_REPO }}
|
||||
magento_version: ${{ matrix.magento }}
|
||||
composer_auth: ${{ secrets.COMPOSER_AUTH }}
|
||||
|
||||
- run: composer update --no-install
|
||||
working-directory: ${{ steps.setup-magento.outputs.path }}
|
||||
env:
|
||||
COMPOSER_AUTH: ${{ secrets.COMPOSER_AUTH }}
|
||||
|
||||
- uses: ./cache-magento
|
||||
with:
|
||||
composer_cache_key: ${{ matrix.magento }}
|
||||
working-directory: ${{ steps.setup-magento.outputs.path }}
|
||||
stamp: true
|
||||
|
||||
- name: Composer install
|
||||
shell: bash
|
||||
run: composer install
|
||||
working-directory: ${{ steps.setup-magento.outputs.path }}
|
||||
env:
|
||||
COMPOSER_AUTH: ${{ secrets.COMPOSER_AUTH }}
|
||||
|
||||
- uses: ./sansec-ecomscan
|
||||
with:
|
||||
license: ${{ secrets.SANSEC_LICENSE_KEY }}
|
||||
path: ${{ steps.setup-magento.outputs.path }}
|
||||
@@ -7,14 +7,14 @@ on:
|
||||
paths:
|
||||
- ".github/workflows/_internal-semver-compare.yaml"
|
||||
- "semver-compare/**"
|
||||
- "!(**/*.md)"
|
||||
- "!**/*.md"
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- ".github/workflows/_internal-semver-compare.yaml"
|
||||
- "semver-compare/**"
|
||||
- "!(**/*.md)"
|
||||
- "!**/*.md"
|
||||
|
||||
jobs:
|
||||
semver-compare:
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
name: Setup Install Test
|
||||
|
||||
on:
|
||||
workflow_dispatch: {}
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- "_test/demo-package/**"
|
||||
- "setup-install/**"
|
||||
- ".github/workflows/_internal-setup-install.yaml"
|
||||
- "supported-version/**"
|
||||
- "!**/*.md"
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- "_test/demo-package/**"
|
||||
- "setup-install/**"
|
||||
- ".github/workflows/_internal-setup-install.yaml"
|
||||
- "supported-version/**"
|
||||
- "!**/*.md"
|
||||
|
||||
jobs:
|
||||
compute_matrix:
|
||||
if: "!startsWith(github.head_ref, 'release-please')"
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
matrix: ${{ steps.supported-version.outputs.matrix }}
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: ./supported-version
|
||||
id: supported-version
|
||||
with:
|
||||
kind: currently-supported
|
||||
include_services: true
|
||||
|
||||
setup-install:
|
||||
needs: compute_matrix
|
||||
strategy:
|
||||
matrix: ${{ fromJSON(needs.compute_matrix.outputs.matrix) }}
|
||||
fail-fast: false
|
||||
runs-on: ${{ matrix.os }}
|
||||
services: ${{ matrix.services }}
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- uses: ./setup-magento
|
||||
id: setup-magento
|
||||
with:
|
||||
php-version: ${{ matrix.php }}
|
||||
tools: composer:v${{ matrix.composer }}
|
||||
mode: extension
|
||||
magento_version: ${{ matrix.magento }}
|
||||
magento_repository: "https://mirror.mage-os.org/"
|
||||
composer_auth: ${{ secrets.COMPOSER_AUTH }}
|
||||
|
||||
- run: composer update --no-install
|
||||
working-directory: ${{ steps.setup-magento.outputs.path }}
|
||||
env:
|
||||
COMPOSER_AUTH: ${{ secrets.COMPOSER_AUTH }}
|
||||
|
||||
- uses: ./cache-magento
|
||||
with:
|
||||
composer_cache_key: ${{ matrix.magento }}
|
||||
working-directory: ${{ steps.setup-magento.outputs.path }}
|
||||
stamp: true
|
||||
|
||||
- name: Add extension repository
|
||||
working-directory: ${{ steps.setup-magento.outputs.path }}
|
||||
run: composer config repositories.local path ${{ github.workspace }}/_test/demo-package
|
||||
|
||||
- name: Get package name
|
||||
id: package
|
||||
run: echo "name=$(jq -r .name ${{ github.workspace }}/_test/demo-package/composer.json)" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Require extension
|
||||
working-directory: ${{ steps.setup-magento.outputs.path }}
|
||||
run: composer require "${{ steps.package.outputs.name }}:@dev" --no-install
|
||||
|
||||
- name: Composer install
|
||||
working-directory: ${{ steps.setup-magento.outputs.path }}
|
||||
run: composer install
|
||||
env:
|
||||
COMPOSER_AUTH: ${{ secrets.COMPOSER_AUTH }}
|
||||
COMPOSER_MIRROR_PATH_REPOS: 1
|
||||
|
||||
- uses: ./setup-install
|
||||
with:
|
||||
services: ${{ toJSON(matrix.services) }}
|
||||
path: ${{ steps.setup-magento.outputs.path }}
|
||||
@@ -9,7 +9,7 @@ on:
|
||||
- "setup-magento/**"
|
||||
- ".github/workflows/_internal-setup-magento.yaml"
|
||||
- "supported-version/**"
|
||||
- "!(**/*.md)"
|
||||
- "!**/*.md"
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
@@ -17,11 +17,10 @@ on:
|
||||
- "setup-magento/**"
|
||||
- ".github/workflows/_internal-setup-magento.yaml"
|
||||
- "supported-version/**"
|
||||
- "!(**/*.md)"
|
||||
- "!**/*.md"
|
||||
|
||||
env:
|
||||
PSEUDO_REPO_FOLDER: ../magento_repo
|
||||
magento_folder: ../magento2
|
||||
magento_folder: _ghamagento
|
||||
MAGENTO_COMPOSER_REPO: "https://mirror.mage-os.org/"
|
||||
|
||||
jobs:
|
||||
@@ -38,6 +37,18 @@ jobs:
|
||||
id: supported-version
|
||||
- run: echo ${{ steps.supported-version.outputs.matrix }}
|
||||
|
||||
compute_matrix_latest:
|
||||
if: "!startsWith(github.head_ref, 'release-please')"
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
matrix: ${{ steps.supported-version.outputs.matrix }}
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: ./supported-version
|
||||
with:
|
||||
kind: latest
|
||||
id: supported-version
|
||||
|
||||
setup-magento-store:
|
||||
needs: compute_matrix
|
||||
strategy:
|
||||
@@ -47,51 +58,124 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- run: |
|
||||
PSEUDO_STORE_FULL_PATH=$(realpath "${{ env.PSEUDO_REPO_FOLDER }}")
|
||||
echo "PSEUDO_STORE_FULL_PATH=$PSEUDO_STORE_FULL_PATH" >> $GITHUB_ENV
|
||||
name: Generate Full Pseudo Store Path
|
||||
shell: bash
|
||||
|
||||
- name: Set PHP Version
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: ${{ matrix.php }}
|
||||
tools: composer:v${{ matrix.composer }}
|
||||
|
||||
- uses: actions/cache@v5
|
||||
id: setup-magento-store-cache
|
||||
with:
|
||||
key: setup-magento-ci | ${{ runner.os }} | ${{ matrix.magento }}
|
||||
path: ${{ env.PSEUDO_STORE_FULL_PATH }}
|
||||
|
||||
- run: composer create-project --repository-url="${{ env.MAGENTO_COMPOSER_REPO }}" "${{ matrix.magento }}" "${{ env.PSEUDO_REPO_FOLDER }}" --no-install
|
||||
- run: composer create-project --repository-url="${{ env.MAGENTO_COMPOSER_REPO }}" "${{ matrix.magento }}" "${{ env.magento_folder }}" --no-install
|
||||
name: Create Store to simulate a real Magento store in a real repo.
|
||||
if: steps.setup-magento-store-cache.outputs.cache-hit != 'true'
|
||||
env:
|
||||
COMPOSER_AUTH: ${{ secrets.COMPOSER_AUTH }}
|
||||
|
||||
- uses: ./fix-magento-install
|
||||
name: Fix Magento Out of Box Install Issues
|
||||
with:
|
||||
magento_directory: ${{ env.PSEUDO_REPO_FOLDER }}
|
||||
if: steps.setup-magento-store-cache.outputs.cache-hit != 'true'
|
||||
magento_directory: ${{ env.magento_folder }}
|
||||
env:
|
||||
COMPOSER_AUTH: ${{ secrets.COMPOSER_AUTH }}
|
||||
|
||||
- run: composer update --no-install
|
||||
working-directory: ${{ env.magento_folder }}
|
||||
env:
|
||||
COMPOSER_AUTH: ${{ secrets.COMPOSER_AUTH }}
|
||||
|
||||
- uses: ./cache-magento
|
||||
with:
|
||||
composer_cache_key: ${{ matrix.magento }}
|
||||
working-directory: ${{ env.magento_folder }}
|
||||
stamp: true
|
||||
|
||||
- run: composer install
|
||||
shell: bash
|
||||
working-directory: "${{ env.PSEUDO_REPO_FOLDER }}"
|
||||
if: steps.setup-magento-store-cache.outputs.cache-hit != 'true'
|
||||
working-directory: "${{ env.magento_folder }}"
|
||||
env:
|
||||
COMPOSER_AUTH: ${{ secrets.COMPOSER_AUTH }}
|
||||
|
||||
- run: git init && git config user.email "you@example.com" && git config user.name "Your Name" && git add . && git commit -m "init" && git clean -fdx
|
||||
working-directory: "${{ env.PSEUDO_REPO_FOLDER }}"
|
||||
if: steps.setup-magento-store-cache.outputs.cache-hit != 'true'
|
||||
|
||||
- run: cp -R ${{ env.PSEUDO_REPO_FOLDER }} ${{ env.magento_folder }}
|
||||
- name: Write Magento .gitignore so git clean strips vendor/generated/var from the cached tree
|
||||
working-directory: "${{ env.magento_folder }}"
|
||||
shell: bash
|
||||
run: |
|
||||
cat > .gitignore <<'EOF'
|
||||
/.buildpath
|
||||
/.cache
|
||||
/.metadata
|
||||
/.project
|
||||
/.settings
|
||||
/.vscode
|
||||
atlassian*
|
||||
/nbproject
|
||||
/robots.txt
|
||||
/pub/robots.txt
|
||||
/sitemap
|
||||
/sitemap.xml
|
||||
/pub/sitemap
|
||||
/pub/sitemap.xml
|
||||
/.idea
|
||||
/.gitattributes
|
||||
/app/config_sandbox
|
||||
/app/etc/config.php
|
||||
/app/etc/env.php
|
||||
/app/code/Magento/TestModule*
|
||||
/lib/internal/flex/uploader/.actionScriptProperties
|
||||
/lib/internal/flex/uploader/.flexProperties
|
||||
/lib/internal/flex/uploader/.project
|
||||
/lib/internal/flex/uploader/.settings
|
||||
/lib/internal/flex/varien/.actionScriptProperties
|
||||
/lib/internal/flex/varien/.flexLibProperties
|
||||
/lib/internal/flex/varien/.project
|
||||
/lib/internal/flex/varien/.settings
|
||||
/node_modules
|
||||
/.grunt
|
||||
/Gruntfile.js
|
||||
/package.json
|
||||
/.php_cs
|
||||
/.php_cs.cache
|
||||
/.php-cs-fixer.php
|
||||
/.php-cs-fixer.cache
|
||||
/grunt-config.json
|
||||
/pub/media/*.*
|
||||
!/pub/media/.htaccess
|
||||
/pub/media/attribute/*
|
||||
!/pub/media/attribute/.htaccess
|
||||
/pub/media/analytics/*
|
||||
/pub/media/catalog/*
|
||||
!/pub/media/catalog/.htaccess
|
||||
/pub/media/customer/*
|
||||
!/pub/media/customer/.htaccess
|
||||
/pub/media/downloadable/*
|
||||
!/pub/media/downloadable/.htaccess
|
||||
/pub/media/favicon/*
|
||||
/pub/media/import/*
|
||||
!/pub/media/import/.htaccess
|
||||
/pub/media/logo/*
|
||||
/pub/media/custom_options/*
|
||||
!/pub/media/custom_options/.htaccess
|
||||
/pub/media/theme/*
|
||||
/pub/media/theme_customization/*
|
||||
!/pub/media/theme_customization/.htaccess
|
||||
/pub/media/wysiwyg/*
|
||||
!/pub/media/wysiwyg/.htaccess
|
||||
/pub/media/tmp/*
|
||||
!/pub/media/tmp/.htaccess
|
||||
/pub/media/captcha/*
|
||||
/pub/media/sitemap/*
|
||||
!/pub/media/sitemap/.htaccess
|
||||
/pub/static/*
|
||||
!/pub/static/.htaccess
|
||||
|
||||
/var/*
|
||||
!/var/.htaccess
|
||||
/vendor/*
|
||||
!/vendor/.htaccess
|
||||
/generated/*
|
||||
!/generated/.htaccess
|
||||
.DS_Store
|
||||
EOF
|
||||
|
||||
- run: git init && git config user.email "you@example.com" && git config user.name "Your Name" && git add . && git commit -m "init" && git clean -fdx
|
||||
working-directory: "${{ env.magento_folder }}"
|
||||
|
||||
- uses: ./setup-magento
|
||||
id: setup-magento
|
||||
@@ -101,10 +185,12 @@ jobs:
|
||||
mode: store
|
||||
working-directory: ${{ env.magento_folder }}
|
||||
composer_auth: ${{ secrets.COMPOSER_AUTH }}
|
||||
|
||||
|
||||
- uses: ./cache-magento
|
||||
with:
|
||||
composer_cache_key: '${{ matrix.magento }}'
|
||||
composer_cache_key: ${{ matrix.magento }}
|
||||
working-directory: ${{ env.magento_folder }}
|
||||
stamp: true
|
||||
|
||||
- run: composer install
|
||||
name: Composer install
|
||||
@@ -114,25 +200,35 @@ jobs:
|
||||
COMPOSER_AUTH: ${{ secrets.COMPOSER_AUTH }}
|
||||
|
||||
setup-magento-extension:
|
||||
if: "!startsWith(github.head_ref, 'release-please')"
|
||||
needs: compute_matrix_latest
|
||||
strategy:
|
||||
matrix: ${{ fromJSON(needs.compute_matrix_latest.outputs.matrix) }}
|
||||
fail-fast: false
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- uses: ./setup-magento
|
||||
id: setup-magento
|
||||
with:
|
||||
php-version: 8.4
|
||||
tools: composer:v2.8
|
||||
php-version: ${{ matrix.php }}
|
||||
tools: composer:v${{ matrix.composer }}
|
||||
mode: extension
|
||||
magento_repository: ${{ env.MAGENTO_COMPOSER_REPO }}
|
||||
composer_auth: ${{ secrets.COMPOSER_AUTH }}
|
||||
magento_version: magento/project-community-edition:2.4.8-p3
|
||||
magento_version: ${{ matrix.magento }}
|
||||
|
||||
- run: composer update --no-install
|
||||
working-directory: ${{ steps.setup-magento.outputs.path }}
|
||||
env:
|
||||
COMPOSER_AUTH: ${{ secrets.COMPOSER_AUTH }}
|
||||
|
||||
- uses: ./cache-magento
|
||||
with:
|
||||
composer_cache_key: 'magento/project-community-edition:2.4.8-p3'
|
||||
|
||||
composer_cache_key: ${{ matrix.magento }}
|
||||
working-directory: ${{ steps.setup-magento.outputs.path }}
|
||||
stamp: true
|
||||
|
||||
- run: composer install
|
||||
name: Composer install
|
||||
shell: bash
|
||||
|
||||
@@ -10,7 +10,7 @@ on:
|
||||
- ".github/workflows/_internal_check_extension.yaml"
|
||||
- ".github/workflows/check-extension.yaml"
|
||||
- "supported-version/**"
|
||||
- "!(**/*.md)"
|
||||
- "!**/*.md"
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
@@ -19,7 +19,7 @@ on:
|
||||
- ".github/workflows/_internal_check_extension.yaml"
|
||||
- ".github/workflows/check-extension.yaml"
|
||||
- "supported-version/**"
|
||||
- "!(**/*.md)"
|
||||
- "!**/*.md"
|
||||
jobs:
|
||||
compute_matrix:
|
||||
if: "!startsWith(github.head_ref, 'release-please')"
|
||||
@@ -39,3 +39,6 @@ jobs:
|
||||
with:
|
||||
path: _test/demo-package
|
||||
matrix: ${{ needs.compute_matrix.outputs.matrix }}
|
||||
stamp: true
|
||||
secrets:
|
||||
composer_auth: ${{ secrets.COMPOSER_AUTH }}
|
||||
|
||||
@@ -8,7 +8,7 @@ on:
|
||||
- ".github/workflows/_internal-supported-version.yaml"
|
||||
- "supported-version/**"
|
||||
- "package-lock.json"
|
||||
- "!(**/*.md)"
|
||||
- "!**/*.md"
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
@@ -16,7 +16,7 @@ on:
|
||||
- ".github/workflows/_internal-supported-version.yaml"
|
||||
- "supported-version/**"
|
||||
- "package-lock.json"
|
||||
- "!(**/*.md)"
|
||||
- "!**/*.md"
|
||||
|
||||
jobs:
|
||||
unit-test:
|
||||
|
||||
@@ -27,24 +27,43 @@ on:
|
||||
composer_cache_key:
|
||||
type: string
|
||||
required: false
|
||||
default: "_mageos"
|
||||
default: ""
|
||||
description: A key to version the composer cache. Can be incremented if you need to bust the cache.
|
||||
|
||||
stamp:
|
||||
type: boolean
|
||||
required: false
|
||||
default: false
|
||||
description: "Cache the vendor/ directory in addition to the Composer download cache."
|
||||
|
||||
secrets:
|
||||
composer_auth:
|
||||
required: false
|
||||
description: "Your composer credentials (typically a stringified json object of the contents of your auth.json)"
|
||||
|
||||
jobs:
|
||||
compute_resolved:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
resolved: ${{ steps.resolve.outputs.resolved }}
|
||||
steps:
|
||||
- uses: graycoreio/github-actions-magento2/resolve-check-config@main
|
||||
id: resolve
|
||||
with:
|
||||
kind: extension
|
||||
matrix: ${{ inputs.matrix }}
|
||||
|
||||
unit-test-extension:
|
||||
runs-on: ${{ matrix.os }}
|
||||
needs: compute_resolved
|
||||
if: ${{ fromJSON(needs.compute_resolved.outputs.resolved)['unit-test-extension'].enabled != false }}
|
||||
strategy:
|
||||
matrix: ${{ fromJSON(inputs.matrix) }}
|
||||
matrix: ${{ fromJSON(needs.compute_resolved.outputs.resolved)['unit-test-extension'].matrix }}
|
||||
fail-fast: ${{ inputs.fail-fast }}
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- uses: graycoreio/github-actions-magento2/setup-magento@v7.0.0-rc.0
|
||||
- uses: graycoreio/github-actions-magento2/setup-magento@main
|
||||
id: setup-magento
|
||||
with:
|
||||
php-version: ${{ matrix.php }}
|
||||
@@ -54,10 +73,6 @@ jobs:
|
||||
magento_repository: ${{ inputs.magento_repository }}
|
||||
composer_auth: ${{ secrets.composer_auth }}
|
||||
|
||||
- uses: graycoreio/github-actions-magento2/cache-magento@v7.0.0-rc.0
|
||||
with:
|
||||
composer_cache_key: ${{ inputs.composer_cache_key }}
|
||||
|
||||
- name: Add extension repository
|
||||
working-directory: ${{ steps.setup-magento.outputs.path }}
|
||||
run: composer config repositories.local path ${{ github.workspace }}/${{ inputs.path }}
|
||||
@@ -69,6 +84,19 @@ jobs:
|
||||
- name: Require extension
|
||||
working-directory: ${{ steps.setup-magento.outputs.path }}
|
||||
run: composer require "${{ steps.package.outputs.name }}:@dev" --no-install
|
||||
env:
|
||||
COMPOSER_AUTH: ${{ secrets.composer_auth }}
|
||||
|
||||
- run: composer update --no-install
|
||||
working-directory: ${{ steps.setup-magento.outputs.path }}
|
||||
env:
|
||||
COMPOSER_AUTH: ${{ secrets.composer_auth }}
|
||||
|
||||
- uses: graycoreio/github-actions-magento2/cache-magento@main
|
||||
with:
|
||||
composer_cache_key: ${{ inputs.composer_cache_key && format('{0} | {1}', inputs.composer_cache_key, matrix.magento) || matrix.magento }}
|
||||
working-directory: ${{ steps.setup-magento.outputs.path }}
|
||||
stamp: ${{ inputs.stamp }}
|
||||
|
||||
- name: Composer install
|
||||
working-directory: ${{ steps.setup-magento.outputs.path }}
|
||||
@@ -98,13 +126,15 @@ jobs:
|
||||
|
||||
compile-extension:
|
||||
runs-on: ${{ matrix.os }}
|
||||
needs: compute_resolved
|
||||
if: ${{ fromJSON(needs.compute_resolved.outputs.resolved)['compile-extension'].enabled != false }}
|
||||
strategy:
|
||||
matrix: ${{ fromJSON(inputs.matrix) }}
|
||||
matrix: ${{ fromJSON(needs.compute_resolved.outputs.resolved)['compile-extension'].matrix }}
|
||||
fail-fast: ${{ inputs.fail-fast }}
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- uses: graycoreio/github-actions-magento2/setup-magento@v7.0.0-rc.0
|
||||
- uses: graycoreio/github-actions-magento2/setup-magento@main
|
||||
id: setup-magento
|
||||
with:
|
||||
php-version: ${{ matrix.php }}
|
||||
@@ -114,10 +144,6 @@ jobs:
|
||||
magento_repository: ${{ inputs.magento_repository }}
|
||||
composer_auth: ${{ secrets.composer_auth }}
|
||||
|
||||
- uses: graycoreio/github-actions-magento2/cache-magento@v7.0.0-rc.0
|
||||
with:
|
||||
composer_cache_key: ${{ inputs.composer_cache_key }}
|
||||
|
||||
- name: Add extension repository
|
||||
working-directory: ${{ steps.setup-magento.outputs.path }}
|
||||
run: composer config repositories.local path ${{ github.workspace }}/${{ inputs.path }}
|
||||
@@ -132,6 +158,17 @@ jobs:
|
||||
env:
|
||||
COMPOSER_AUTH: ${{ secrets.composer_auth }}
|
||||
|
||||
- run: composer update --no-install
|
||||
working-directory: ${{ steps.setup-magento.outputs.path }}
|
||||
env:
|
||||
COMPOSER_AUTH: ${{ secrets.composer_auth }}
|
||||
|
||||
- uses: graycoreio/github-actions-magento2/cache-magento@main
|
||||
with:
|
||||
composer_cache_key: ${{ inputs.composer_cache_key && format('{0} | {1}', inputs.composer_cache_key, matrix.magento) || matrix.magento }}
|
||||
working-directory: ${{ steps.setup-magento.outputs.path }}
|
||||
stamp: ${{ inputs.stamp }}
|
||||
|
||||
- name: Composer install
|
||||
working-directory: ${{ steps.setup-magento.outputs.path }}
|
||||
run: composer install
|
||||
@@ -139,18 +176,25 @@ jobs:
|
||||
COMPOSER_AUTH: ${{ secrets.composer_auth }}
|
||||
COMPOSER_MIRROR_PATH_REPOS: 1
|
||||
|
||||
- name: Enable all modules
|
||||
working-directory: ${{ steps.setup-magento.outputs.path }}
|
||||
run: php bin/magento module:enable --all
|
||||
- uses: graycoreio/github-actions-magento2/setup-di-compile@main
|
||||
with:
|
||||
path: ${{ steps.setup-magento.outputs.path }}
|
||||
|
||||
- name: Compile
|
||||
working-directory: ${{ steps.setup-magento.outputs.path }}
|
||||
run: php bin/magento setup:di:compile
|
||||
compute_latest_matrix:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
matrix: ${{ steps.supported-version.outputs.matrix }}
|
||||
steps:
|
||||
- uses: graycoreio/github-actions-magento2/supported-version@main
|
||||
id: supported-version
|
||||
with:
|
||||
kind: latest
|
||||
|
||||
coding-standard:
|
||||
runs-on: ${{ matrix.os }}
|
||||
needs: compute_latest_matrix
|
||||
strategy:
|
||||
matrix: ${{ fromJSON(inputs.matrix) }}
|
||||
matrix: ${{ fromJSON(needs.compute_latest_matrix.outputs.matrix) }}
|
||||
fail-fast: ${{ inputs.fail-fast }}
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
@@ -161,21 +205,27 @@ jobs:
|
||||
tools: composer:v${{ matrix.composer }}
|
||||
coverage: none
|
||||
|
||||
- uses: graycoreio/github-actions-magento2/coding-standard@v7.0.0-rc.0
|
||||
- uses: graycoreio/github-actions-magento2/cache-magento@main
|
||||
with:
|
||||
composer_cache_key: ${{ inputs.composer_cache_key && format('{0} | {1}', inputs.composer_cache_key, matrix.magento) || matrix.magento }}
|
||||
|
||||
- uses: graycoreio/github-actions-magento2/coding-standard@main
|
||||
with:
|
||||
path: ${{ inputs.path }}
|
||||
composer_auth: ${{ secrets.composer_auth }}
|
||||
|
||||
integration_test:
|
||||
runs-on: ${{ matrix.os }}
|
||||
needs: compute_resolved
|
||||
if: ${{ fromJSON(needs.compute_resolved.outputs.resolved)['integration_test'].enabled != false }}
|
||||
strategy:
|
||||
matrix: ${{ fromJSON(inputs.matrix) }}
|
||||
matrix: ${{ fromJSON(needs.compute_resolved.outputs.resolved)['integration_test'].matrix }}
|
||||
fail-fast: ${{ inputs.fail-fast }}
|
||||
services: ${{ matrix.services }}
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- uses: graycoreio/github-actions-magento2/setup-magento@v7.0.0-rc.0
|
||||
- uses: graycoreio/github-actions-magento2/setup-magento@main
|
||||
id: setup-magento
|
||||
with:
|
||||
php-version: ${{ matrix.php }}
|
||||
@@ -185,10 +235,6 @@ jobs:
|
||||
magento_repository: ${{ inputs.magento_repository }}
|
||||
composer_auth: ${{ secrets.composer_auth }}
|
||||
|
||||
- uses: graycoreio/github-actions-magento2/cache-magento@v7.0.0-rc.0
|
||||
with:
|
||||
composer_cache_key: ${{ inputs.composer_cache_key }}
|
||||
|
||||
- name: Add extension repository
|
||||
working-directory: ${{ steps.setup-magento.outputs.path }}
|
||||
run: composer config repositories.local path ${{ github.workspace }}/${{ inputs.path }}
|
||||
@@ -203,6 +249,17 @@ jobs:
|
||||
env:
|
||||
COMPOSER_AUTH: ${{ secrets.composer_auth }}
|
||||
|
||||
- run: composer update --no-install
|
||||
working-directory: ${{ steps.setup-magento.outputs.path }}
|
||||
env:
|
||||
COMPOSER_AUTH: ${{ secrets.composer_auth }}
|
||||
|
||||
- uses: graycoreio/github-actions-magento2/cache-magento@main
|
||||
with:
|
||||
composer_cache_key: ${{ inputs.composer_cache_key && format('{0} | {1}', inputs.composer_cache_key, matrix.magento) || matrix.magento }}
|
||||
working-directory: ${{ steps.setup-magento.outputs.path }}
|
||||
stamp: ${{ inputs.stamp }}
|
||||
|
||||
- name: Composer install
|
||||
working-directory: ${{ steps.setup-magento.outputs.path }}
|
||||
run: composer install
|
||||
@@ -210,7 +267,7 @@ jobs:
|
||||
COMPOSER_AUTH: ${{ secrets.composer_auth }}
|
||||
COMPOSER_MIRROR_PATH_REPOS: 1
|
||||
|
||||
- uses: graycoreio/github-actions-magento2/get-magento-version@v7.0.0-rc.0
|
||||
- uses: graycoreio/github-actions-magento2/get-magento-version@main
|
||||
id: magento-version
|
||||
with:
|
||||
working-directory: ${{ steps.setup-magento.outputs.path }}
|
||||
@@ -244,7 +301,7 @@ jobs:
|
||||
run: ../../../vendor/bin/phpunit -c phpunit.xml.dist --testsuite Extension_Integration_Tests
|
||||
|
||||
- name: Upload test sandbox dir
|
||||
uses: actions/upload-artifact@v6
|
||||
uses: actions/upload-artifact@v7
|
||||
if: failure()
|
||||
with:
|
||||
name: sandbox-data-${{ steps.magento-version.outputs.version }}
|
||||
|
||||
@@ -0,0 +1,249 @@
|
||||
name: MageCheck Store
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
path:
|
||||
type: string
|
||||
required: false
|
||||
default: "."
|
||||
description: "The folder of the Magento store that you are testing."
|
||||
|
||||
composer_cache_key:
|
||||
type: string
|
||||
required: false
|
||||
default: "_mageos"
|
||||
description: A key to version the composer cache. Can be incremented if you need to bust the cache.
|
||||
|
||||
store_artifact_name:
|
||||
type: string
|
||||
required: false
|
||||
default: ""
|
||||
description: "If provided, download store files from this artifact instead of using actions/checkout."
|
||||
|
||||
stamp:
|
||||
type: boolean
|
||||
required: false
|
||||
default: false
|
||||
description: "Cache the vendor/ directory in addition to the Composer download cache."
|
||||
|
||||
secrets:
|
||||
composer_auth:
|
||||
required: false
|
||||
description: "Your composer credentials (typically a stringified json object of the contents of your auth.json)"
|
||||
|
||||
jobs:
|
||||
compute_matrix:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
resolved: ${{ steps.resolve.outputs.resolved }}
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
if: inputs.store_artifact_name == ''
|
||||
|
||||
- uses: actions/download-artifact@v8
|
||||
if: inputs.store_artifact_name != ''
|
||||
with:
|
||||
name: ${{ inputs.store_artifact_name }}
|
||||
path: ${{ inputs.path }}
|
||||
|
||||
- uses: graycoreio/github-actions-magento2/get-magento-version@main
|
||||
id: get-magento-version
|
||||
with:
|
||||
working-directory: ${{ inputs.path }}
|
||||
|
||||
- uses: graycoreio/github-actions-magento2/supported-version@main
|
||||
id: supported-version
|
||||
with:
|
||||
project: ${{ steps.get-magento-version.outputs.supported_version_project }}
|
||||
kind: custom
|
||||
custom_versions: ${{ steps.get-magento-version.outputs.project }}:${{ fromJSON(steps.get-magento-version.outputs.version) }}
|
||||
|
||||
- uses: graycoreio/github-actions-magento2/resolve-check-config@main
|
||||
id: resolve
|
||||
with:
|
||||
kind: store
|
||||
matrix: ${{ steps.supported-version.outputs.matrix }}
|
||||
|
||||
unit-test:
|
||||
runs-on: ${{ matrix.os }}
|
||||
needs: compute_matrix
|
||||
if: ${{ fromJSON(needs.compute_matrix.outputs.resolved)['unit-test'].enabled != false }}
|
||||
strategy:
|
||||
matrix: ${{ fromJSON(needs.compute_matrix.outputs.resolved)['unit-test'].matrix }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
if: inputs.store_artifact_name == ''
|
||||
|
||||
- uses: actions/download-artifact@v8
|
||||
if: inputs.store_artifact_name != ''
|
||||
with:
|
||||
name: ${{ inputs.store_artifact_name }}
|
||||
path: ${{ inputs.path }}
|
||||
|
||||
- uses: graycoreio/github-actions-magento2/setup-magento@main
|
||||
id: setup-magento
|
||||
with:
|
||||
php-version: ${{ matrix.php }}
|
||||
tools: composer:v${{ matrix.composer }}
|
||||
mode: store
|
||||
working-directory: ${{ inputs.path }}
|
||||
composer_auth: ${{ secrets.composer_auth }}
|
||||
|
||||
- uses: graycoreio/github-actions-magento2/cache-magento@main
|
||||
with:
|
||||
composer_cache_key: ${{ inputs.composer_cache_key }}
|
||||
working-directory: ${{ steps.setup-magento.outputs.path }}
|
||||
stamp: ${{ inputs.stamp }}
|
||||
|
||||
- name: Composer install
|
||||
working-directory: ${{ steps.setup-magento.outputs.path }}
|
||||
run: composer install
|
||||
env:
|
||||
COMPOSER_AUTH: ${{ secrets.composer_auth }}
|
||||
|
||||
- name: Configure phpunit.xml.dist
|
||||
working-directory: ${{ steps.setup-magento.outputs.path }}
|
||||
run: |
|
||||
mkdir -p app/code
|
||||
|
||||
cat > /tmp/testsuite.xml << 'EOF'
|
||||
<testsuite name="Store_Unit_Tests">
|
||||
<directory>../../../app/code/*/*/Test/Unit</directory>
|
||||
</testsuite>
|
||||
EOF
|
||||
sed -i '/<testsuites>/r /tmp/testsuite.xml' dev/tests/unit/phpunit.xml.dist
|
||||
|
||||
## PHPUnit 12 (Magento 2.4.9) implicitly enables failOnEmptyTestSuite when --testsuite is passed.
|
||||
## Default it off only when the consumer hasn't set it themselves, so we don't clobber explicit configuration.
|
||||
if ! grep -q 'failOnEmptyTestSuite=' dev/tests/unit/phpunit.xml.dist; then
|
||||
sed -i 's|<phpunit |<phpunit failOnEmptyTestSuite="false" |' dev/tests/unit/phpunit.xml.dist
|
||||
fi
|
||||
|
||||
## Disable allure (See https://github.com/magento/magento2/issues/36702 )
|
||||
sed -i '/<extensions>/,/<\/extensions>/d' dev/tests/unit/phpunit.xml.dist
|
||||
|
||||
- name: Run unit tests
|
||||
working-directory: ${{ steps.setup-magento.outputs.path }}
|
||||
run: vendor/bin/phpunit -c dev/tests/unit/phpunit.xml.dist --testsuite Store_Unit_Tests
|
||||
|
||||
coding-standard:
|
||||
runs-on: ${{ matrix.os }}
|
||||
needs: compute_matrix
|
||||
if: ${{ fromJSON(needs.compute_matrix.outputs.resolved)['coding-standard'].enabled != false }}
|
||||
strategy:
|
||||
matrix: ${{ fromJSON(needs.compute_matrix.outputs.resolved)['coding-standard'].matrix }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
if: inputs.store_artifact_name == ''
|
||||
|
||||
- uses: actions/download-artifact@v8
|
||||
if: inputs.store_artifact_name != ''
|
||||
with:
|
||||
name: ${{ inputs.store_artifact_name }}
|
||||
path: ${{ inputs.path }}
|
||||
|
||||
- uses: graycoreio/github-actions-magento2/setup-magento@main
|
||||
id: setup-magento
|
||||
with:
|
||||
php-version: ${{ matrix.php }}
|
||||
tools: composer:v${{ matrix.composer }}
|
||||
mode: store
|
||||
working-directory: ${{ inputs.path }}
|
||||
composer_auth: ${{ secrets.composer_auth }}
|
||||
|
||||
- uses: graycoreio/github-actions-magento2/cache-magento@main
|
||||
with:
|
||||
composer_cache_key: ${{ inputs.composer_cache_key }}
|
||||
working-directory: ${{ steps.setup-magento.outputs.path }}
|
||||
stamp: ${{ inputs.stamp }}
|
||||
|
||||
- name: Composer install
|
||||
working-directory: ${{ steps.setup-magento.outputs.path }}
|
||||
run: composer install
|
||||
env:
|
||||
COMPOSER_AUTH: ${{ secrets.composer_auth }}
|
||||
|
||||
- name: Create default phpcs.xml if none exists
|
||||
working-directory: ${{ steps.setup-magento.outputs.path }}
|
||||
run: |
|
||||
mkdir -p app/code
|
||||
if [ ! -f .phpcs.xml ] && [ ! -f phpcs.xml ] && [ ! -f .phpcs.xml.dist ] && [ ! -f phpcs.xml.dist ]; then
|
||||
cat > phpcs.xml << 'EOF'
|
||||
<ruleset name="Store">
|
||||
<rule ref="Magento2"/>
|
||||
<file>app/code</file>
|
||||
</ruleset>
|
||||
EOF
|
||||
fi
|
||||
|
||||
- uses: graycoreio/github-actions-magento2/coding-standard@main
|
||||
with:
|
||||
path: ${{ steps.setup-magento.outputs.path }}
|
||||
composer_auth: ${{ secrets.composer_auth }}
|
||||
|
||||
smoke-test:
|
||||
runs-on: ${{ matrix.os }}
|
||||
needs: compute_matrix
|
||||
if: ${{ fromJSON(needs.compute_matrix.outputs.resolved)['smoke-test'].enabled != false }}
|
||||
services: ${{ matrix.services }}
|
||||
strategy:
|
||||
matrix: ${{ fromJSON(needs.compute_matrix.outputs.resolved)['smoke-test'].matrix }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
if: inputs.store_artifact_name == ''
|
||||
|
||||
- uses: actions/download-artifact@v8
|
||||
if: inputs.store_artifact_name != ''
|
||||
with:
|
||||
name: ${{ inputs.store_artifact_name }}
|
||||
path: ${{ inputs.path }}
|
||||
|
||||
- uses: graycoreio/github-actions-magento2/setup-magento@main
|
||||
id: setup-magento
|
||||
with:
|
||||
php-version: ${{ matrix.php }}
|
||||
tools: composer:v${{ matrix.composer }}
|
||||
mode: store
|
||||
working-directory: ${{ inputs.path }}
|
||||
composer_auth: ${{ secrets.composer_auth }}
|
||||
|
||||
- uses: graycoreio/github-actions-magento2/cache-magento@main
|
||||
with:
|
||||
composer_cache_key: ${{ inputs.composer_cache_key }}
|
||||
working-directory: ${{ steps.setup-magento.outputs.path }}
|
||||
stamp: ${{ inputs.stamp }}
|
||||
|
||||
- name: Composer install
|
||||
working-directory: ${{ steps.setup-magento.outputs.path }}
|
||||
run: composer install
|
||||
env:
|
||||
COMPOSER_AUTH: ${{ secrets.composer_auth }}
|
||||
|
||||
- uses: graycoreio/github-actions-magento2/setup-install@main
|
||||
id: setup-install
|
||||
with:
|
||||
services: ${{ toJSON(matrix.services) }}
|
||||
path: ${{ steps.setup-magento.outputs.path }}
|
||||
container_id: ${{ job.services['php-fpm'].id }}
|
||||
extra_args: --magento-init-params=MAGE_MODE=developer
|
||||
|
||||
- uses: graycoreio/github-actions-magento2/configure-service-nginx@main
|
||||
with:
|
||||
container_id: ${{ job.services.nginx.id }}
|
||||
magento_path: ${{ inputs.path }}
|
||||
|
||||
- uses: graycoreio/github-actions-magento2/smoke-test@main
|
||||
if: contains(fromJSON(needs.compute_matrix.outputs.resolved)['smoke-test'].probes, 'page')
|
||||
with:
|
||||
kind: page
|
||||
|
||||
## graphql is opt-in: editions without GraphQL modules (e.g. mage-os
|
||||
## minimal) have no /graphql endpoint. Enable it per store via
|
||||
## `.github/check-store.json` -> jobs.smoke-test.probes: ["page", "graphql"].
|
||||
- uses: graycoreio/github-actions-magento2/smoke-test@main
|
||||
if: contains(fromJSON(needs.compute_matrix.outputs.resolved)['smoke-test'].probes, 'graphql')
|
||||
with:
|
||||
kind: graphql
|
||||
@@ -82,7 +82,7 @@ jobs:
|
||||
COMPOSER_AUTH: ${{ secrets.composer_auth }}
|
||||
name: Create Magento ${{ matrix.magento }} Project
|
||||
|
||||
- uses: graycoreio/github-actions-magento2/get-magento-version@v7.0.0-rc.0
|
||||
- uses: graycoreio/github-actions-magento2/get-magento-version@main
|
||||
id: magento-version
|
||||
with:
|
||||
working-directory: ${{ inputs.magento_directory }}
|
||||
@@ -182,7 +182,7 @@ jobs:
|
||||
name: Run Integration Tests
|
||||
|
||||
- name: Upload test sandbox dir
|
||||
uses: actions/upload-artifact@v6
|
||||
uses: actions/upload-artifact@v7
|
||||
if: failure()
|
||||
with:
|
||||
name: sandbox-data-${{ steps.magento-version.outputs.version }}
|
||||
|
||||
@@ -5,6 +5,16 @@ on:
|
||||
branches:
|
||||
- main
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
release-mode:
|
||||
description: 'auto = follow conventional commits; rc = bump the rc suffix; graduate = graduate the current rc to a stable release.'
|
||||
type: choice
|
||||
required: false
|
||||
default: auto
|
||||
options:
|
||||
- auto
|
||||
- rc
|
||||
- graduate
|
||||
|
||||
env:
|
||||
RELEASE_BRANCH: release-please--branches--main--components--github-actions-magento2
|
||||
@@ -18,9 +28,10 @@ jobs:
|
||||
releases_created: ${{ steps.release.outputs.releases_created }}
|
||||
steps:
|
||||
- id: release
|
||||
uses: googleapis/release-please-action@v4
|
||||
uses: googleapis/release-please-action@v5
|
||||
with:
|
||||
token: ${{ secrets.GRAYCORE_GITHUB_TOKEN }}
|
||||
config-file: ${{ inputs.release-mode == 'rc' && 'release-please-config.rc.json' || (inputs.release-mode == 'graduate' && 'release-please-config.graduate.json' || 'release-please-config.json') }}
|
||||
|
||||
- name: Check if release branch exists
|
||||
id: branch-check
|
||||
|
||||
@@ -1 +1 @@
|
||||
{".":"7.0.0-rc.0"}
|
||||
{".":"8.5.0"}
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
# AGENTS.md
|
||||
|
||||
## Project Overview
|
||||
|
||||
`github-actions-magento2` — a GitHub Actions toolkit for Magento 2 development. Provides reusable composite actions and reusable workflows that Magento module and store developers call from their own CI pipelines.
|
||||
|
||||
## Repository Structure
|
||||
|
||||
```
|
||||
.github/workflows/ # Reusable workflows and internal CI workflows
|
||||
_test/demo-package/ # Test fixture used by internal CI workflows
|
||||
docs/ # General documentation
|
||||
other-root-level-folders # Individual GitHub Actions (all are external/public)
|
||||
```
|
||||
|
||||
Most actions are **composite** (bash scripts in `action.yml`). Two are **TypeScript bundled**: `supported-version` and `setup-install`.
|
||||
|
||||
## Commands
|
||||
|
||||
```bash
|
||||
# Run all tests
|
||||
npm ci
|
||||
npm test
|
||||
|
||||
# Run tests for a single package
|
||||
cd actionName && npm test
|
||||
|
||||
# Build a TypeScript action (must be committed after source changes)
|
||||
cd supported-version && npm run build
|
||||
cd setup-install && npm run build
|
||||
```
|
||||
|
||||
Build uses `esbuild` and outputs `dist/index.js`. The `dist/` file **must be committed** — GitHub Actions runs the bundled output directly.
|
||||
|
||||
## Code Style
|
||||
|
||||
- TypeScript with strict settings
|
||||
- ESLint: `eslint:recommended` + `@typescript-eslint/recommended`
|
||||
- No comments unless the "why" is non-obvious
|
||||
- Conventional commits
|
||||
|
||||
## Hard Rules (all agents)
|
||||
|
||||
- Never edit `CHANGELOG.md` — managed by release-please
|
||||
- Never commit TypeScript source changes without also committing the rebuilt `dist/index.js`
|
||||
- Never add external runtime dependencies to TypeScript actions without flagging bundle size impact
|
||||
- Never call `_internal-*` workflows from external repositories
|
||||
|
||||
---
|
||||
|
||||
## @test-agent
|
||||
|
||||
Writes and updates Jest specs for TypeScript actions (`supported-version`, `setup-install`). Scope is limited to `**/*.spec.ts` files.
|
||||
|
||||
### Style
|
||||
|
||||
Test observable behavior, not implementation details. No filesystem mocks — use real temp dirs if needed. No network access.
|
||||
|
||||
```ts
|
||||
import { validateKind } from "./validate-kinds";
|
||||
|
||||
describe('validateKind', () => {
|
||||
it('returns `true` if its a valid kind', () => {
|
||||
expect(validateKind("latest")).toBe(true);
|
||||
});
|
||||
|
||||
it('throws a helpful exception if its an invalid kind', () => {
|
||||
expect(() => validateKind(<any>"taco")).toThrowError();
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
### Never
|
||||
|
||||
- Test implementation details
|
||||
- Mock the filesystem
|
||||
- Write tests that require network access
|
||||
|
||||
---
|
||||
|
||||
## @supported-version-agent
|
||||
|
||||
Manages the Magento/Mage-OS version compatibility data in `supported-version/src/versions/`. Scope is limited to those JSON files.
|
||||
|
||||
### After every edit
|
||||
|
||||
Run `npm test` inside `supported-version/` before declaring done.
|
||||
|
||||
### Never
|
||||
|
||||
- Remove a version entry — only add or mark end-of-life
|
||||
- Guess version compatibility — only use data from official Magento/Mage-OS release notes
|
||||
- Edit TypeScript source in `supported-version/src/`
|
||||
|
||||
---
|
||||
|
||||
## @workflow-agent
|
||||
|
||||
Owns all externally-facing aspects of the repo: every root-level composite action and the three public reusable workflows (`integration.yaml`, `check-extension.yaml`, `check-store.yaml`). Changes here affect downstream callers.
|
||||
|
||||
### Boundaries
|
||||
|
||||
**Free to act** — implementation changes that do not alter the public interface (inputs, outputs, default behavior)
|
||||
|
||||
**Ask first** — any change to inputs, outputs, or default behavior of an external action or reusable workflow
|
||||
|
||||
**Never**
|
||||
- Remove or rename an existing input/output without a major version bump
|
||||
- Change the default value of an existing input
|
||||
- Modify `_internal-*` workflows (out of scope)
|
||||
- Add a runtime dependency to a TypeScript action without flagging bundle size impact
|
||||
+137
@@ -2,6 +2,143 @@
|
||||
|
||||
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
||||
|
||||
## [8.5.0](https://github.com/graycoreio/github-actions-magento2/compare/v8.4.0...v8.5.0) (2026-05-27)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **setup-install:** run with --no-interaction ([32a5fd2](https://github.com/graycoreio/github-actions-magento2/commit/32a5fd2badfe558e7dced9606765d0d44632c6f0))
|
||||
|
||||
## [8.4.0](https://github.com/graycoreio/github-actions-magento2/compare/v8.3.0...v8.4.0) (2026-05-25)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* remove rabbitmq from supported-version for mage-os/minimal ([83f9433](https://github.com/graycoreio/github-actions-magento2/commit/83f9433da0d7f20efbf090fd8ed75a0a39000797))
|
||||
|
||||
## [8.3.0](https://github.com/graycoreio/github-actions-magento2/compare/v8.2.0...v8.3.0) (2026-05-25)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **check-store:** use the project when computing underlying version requirements ([fa8e597](https://github.com/graycoreio/github-actions-magento2/commit/fa8e59736563d5969f5c8ebaccd23c48f0628721))
|
||||
* **get-magento-version:** add support for MageOS minimal distro ([863444a](https://github.com/graycoreio/github-actions-magento2/commit/863444afbd137d32157392b964f06503f021ee6c))
|
||||
* **get-magento-version:** emit supported-version project name as an output ([1ea5a10](https://github.com/graycoreio/github-actions-magento2/commit/1ea5a10ef67d6fda8d10e078895adc9bea434477))
|
||||
* **supported-version:** add support for MageOS 3 ([1e63c01](https://github.com/graycoreio/github-actions-magento2/commit/1e63c019edb63ee0bcd4576b4125b73520ca8864))
|
||||
* **supported-version:** add support for MageOS Minimal edition ([befe080](https://github.com/graycoreio/github-actions-magento2/commit/befe0807f7636c125d7e650f2d08012b28554a54))
|
||||
|
||||
## [8.2.0](https://github.com/graycoreio/github-actions-magento2/compare/v8.1.0...v8.2.0) (2026-05-17)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **check-extension:** allow configuraton via .github/check-extension.json ([#269](https://github.com/graycoreio/github-actions-magento2/issues/269)) ([0bf08ef](https://github.com/graycoreio/github-actions-magento2/commit/0bf08ef69291090e5fe3e3d47cb432c6c9107f30))
|
||||
* **resolve-check-config:** defined required integration test services required ([#269](https://github.com/graycoreio/github-actions-magento2/issues/269)) ([35c1ace](https://github.com/graycoreio/github-actions-magento2/commit/35c1ace2bc68be1356dc6565a8a05ff02e33d75d))
|
||||
|
||||
## [8.1.0](https://github.com/graycoreio/github-actions-magento2/compare/v8.0.0...v8.1.0) (2026-05-17)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **check-store:** add smoke-test action and use resolve-check-config ([#255](https://github.com/graycoreio/github-actions-magento2/issues/255)) ([e39dd46](https://github.com/graycoreio/github-actions-magento2/commit/e39dd46f9c53a0d2625cd5d19ad1cf18565b8c5c))
|
||||
* **configure-service-nginx:** add ability to adjust nginx conf after init ([#255](https://github.com/graycoreio/github-actions-magento2/issues/255)) ([0c7d14d](https://github.com/graycoreio/github-actions-magento2/commit/0c7d14d88573d92c81654b1107ef6a9e4d918cff))
|
||||
* **resolve-check-config:** add ability to use a config file to adjust jobs ([#255](https://github.com/graycoreio/github-actions-magento2/issues/255)) ([b98313e](https://github.com/graycoreio/github-actions-magento2/commit/b98313e10044a0a6a04546d3ff8ebe3a3f284f5b))
|
||||
* **setup-install:** add a container_id input to run setup:install against a specific container ([#255](https://github.com/graycoreio/github-actions-magento2/issues/255)) ([6d4ca8d](https://github.com/graycoreio/github-actions-magento2/commit/6d4ca8d669164d840d99e8af721309abb9f204ea))
|
||||
* **smoke-test:** add simple smoke test action ([#255](https://github.com/graycoreio/github-actions-magento2/issues/255)) ([b790da1](https://github.com/graycoreio/github-actions-magento2/commit/b790da18597e58a9013cc0f7e2c923f08c82f813))
|
||||
* **supported-version:** add service_preferences and support for php-fpm and nginx ([#255](https://github.com/graycoreio/github-actions-magento2/issues/255)) ([e89f6ad](https://github.com/graycoreio/github-actions-magento2/commit/e89f6ad2e08fcaa03cba92c8371e60ba67b3cf62))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **check-extension:** only run coding-standard on most recent version of Magento ([#265](https://github.com/graycoreio/github-actions-magento2/issues/265)) ([8e82fcc](https://github.com/graycoreio/github-actions-magento2/commit/8e82fcc89354c83523781c1f5fd4622dec19ca7b))
|
||||
|
||||
## [8.0.0](https://github.com/graycoreio/github-actions-magento2/compare/v8.0.0-rc.2...v8.0.0) (2026-05-14)
|
||||
|
||||
|
||||
### Miscellaneous Chores
|
||||
|
||||
* graduate 8.0.0-rc.2 to 8.0.0 ([#262](https://github.com/graycoreio/github-actions-magento2/issues/262)) ([e6bb7be](https://github.com/graycoreio/github-actions-magento2/commit/e6bb7be5248a1431f06c07986066ab154c9d8531))
|
||||
|
||||
## [8.0.0-rc.2](https://github.com/graycoreio/github-actions-magento2/compare/v8.0.0-rc.1...v8.0.0-rc.2) (2026-05-13)
|
||||
|
||||
|
||||
### ⚠ BREAKING CHANGES
|
||||
|
||||
* **supported-versions:** forcibly bump all packages to the latest relevant release line of composer for LogLeak
|
||||
* **supported-versions:** This release brings support for the v2.4.9 version of Magento. This also brings backwards-incompatible infrastructure changes for the patch versions of Magento. See https://github.com/graycoreio/github-actions-magento2/pull/258 for more information.
|
||||
|
||||
### Features
|
||||
|
||||
* **check-extension:** pass along COMPOSER_AUTH where needed ([#258](https://github.com/graycoreio/github-actions-magento2/issues/258)) ([c5221f0](https://github.com/graycoreio/github-actions-magento2/commit/c5221f0d68b7ecc892b7718326eabc6f093c108f))
|
||||
* **supported-versions:** forcibly bump all packages to the latest relevant release line of composer for LogLeak ([d1a31d2](https://github.com/graycoreio/github-actions-magento2/commit/d1a31d260dc54556ebd1ea4fb2e1764ad637694a))
|
||||
* **supported-versions:** updates for 2.4.9, 2.4.8-p5, 2.4.7-p19, 2.4.6-p15 ([#258](https://github.com/graycoreio/github-actions-magento2/issues/258)) ([d37f001](https://github.com/graycoreio/github-actions-magento2/commit/d37f001ab6607d2c23751db12d21a7a9e69543f3))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **check-store:** prevent error in phpunit 12 if no tests exists ([#258](https://github.com/graycoreio/github-actions-magento2/issues/258)) ([4fc491b](https://github.com/graycoreio/github-actions-magento2/commit/4fc491bc1a26b7b7089b562db5d4e4a89b6d0744))
|
||||
|
||||
## [8.0.0-rc.1](https://github.com/graycoreio/github-actions-magento2/compare/v8.0.0-rc.0...v8.0.0-rc.1) (2026-05-10)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **setup-magento:** prevent Magento dir from being mirrored into vendor ([22627e1](https://github.com/graycoreio/github-actions-magento2/commit/22627e100059b090adaf2484a09db2d5568492ce))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **check-extension:** prevent recursively mirroring _ghamagento into _ghamagento ([b0131f0](https://github.com/graycoreio/github-actions-magento2/commit/b0131f0fa08dae3b41c6ae476ae5f81ac654c68b))
|
||||
* **check-store:** only run unit tests for unit tests ([ef06f45](https://github.com/graycoreio/github-actions-magento2/commit/ef06f4566ba2bd9e132d0fe85becb5a96b58aa38))
|
||||
|
||||
## [8.0.0-rc.0](https://github.com/graycoreio/github-actions-magento2/compare/v7.0.0...v8.0.0-rc.0) (2026-05-10)
|
||||
|
||||
|
||||
### ⚠ BREAKING CHANGES
|
||||
|
||||
* **setup-magento:** Previously, when using setup-magento in extension mode, the magento 2 repo root was ../magento2 (outside of the extension folder). Due to interactions with `cache-magento` we need to keep magento inside the GITHUB_WORKSPACE (the root repo). We now do this in the `_ghamagento` folder. If you rely on the `steps.setup-magento.outputs.path` nothing changes for you. But, if you hardcoded the path, it's likely broken.
|
||||
|
||||
### Features
|
||||
|
||||
* **cache-magento:** add stamp caching for vendor/ directory ([#245](https://github.com/graycoreio/github-actions-magento2/issues/245)) ([8d00f81](https://github.com/graycoreio/github-actions-magento2/commit/8d00f8149abb5fe9dc9cc87775b108f30284cf21))
|
||||
* **cache-magento:** include runner.os in the cache key ([#245](https://github.com/graycoreio/github-actions-magento2/issues/245)) ([2d7238d](https://github.com/graycoreio/github-actions-magento2/commit/2d7238de14a6ce3657b430ebd89f60b4cc341a09))
|
||||
* **check-extension:** enable stamp caching ([#248](https://github.com/graycoreio/github-actions-magento2/issues/248)) ([baef64b](https://github.com/graycoreio/github-actions-magento2/commit/baef64bc0a235dc92cb81c10afbd22e70e6623f2))
|
||||
* **check-extension:** use setup-di-compile action in compile-extension job ([#240](https://github.com/graycoreio/github-actions-magento2/issues/240)) ([6a520d4](https://github.com/graycoreio/github-actions-magento2/commit/6a520d49fd4ba3f33151dbb8c12dfd3be47630ab))
|
||||
* **check-store:** enable stamp caching ([#247](https://github.com/graycoreio/github-actions-magento2/issues/247)) ([59f87b6](https://github.com/graycoreio/github-actions-magento2/commit/59f87b6b2e4e0007e041c82329291012ee95ce61))
|
||||
* **check-store:** introduce new check-store workflow ([#241](https://github.com/graycoreio/github-actions-magento2/issues/241)) ([d311df7](https://github.com/graycoreio/github-actions-magento2/commit/d311df79661d13ab252eb681600608ed821c78fd))
|
||||
* **get-magento-version:** pull version from lockfile if it exists ([#242](https://github.com/graycoreio/github-actions-magento2/issues/242)) ([87989bb](https://github.com/graycoreio/github-actions-magento2/commit/87989bb250aab72274ad9f71481f70f0a8d8ac1e))
|
||||
* **sansec-ecomscan:** add sansec ecomscan feature ([#235](https://github.com/graycoreio/github-actions-magento2/issues/235)) ([3c0a90f](https://github.com/graycoreio/github-actions-magento2/commit/3c0a90f92ba4e3aaa6854bc98d451fde7340877d))
|
||||
* **sansec-ecomscan:** skip server checks by default ([#238](https://github.com/graycoreio/github-actions-magento2/issues/238)) ([bbd8307](https://github.com/graycoreio/github-actions-magento2/commit/bbd830745f9b752d308f4ef1b8fdc48cea10e5ba))
|
||||
* **setup-di-compile:** restore setup-di-compile as a lean action ([#239](https://github.com/graycoreio/github-actions-magento2/issues/239)) ([212f9a8](https://github.com/graycoreio/github-actions-magento2/commit/212f9a8e86e2c214910e26c3ea19eb90b9aafc4b))
|
||||
* **setup-install:** add new setup-install action ([#237](https://github.com/graycoreio/github-actions-magento2/issues/237)) ([e31f6f6](https://github.com/graycoreio/github-actions-magento2/commit/e31f6f656a2e24afcb95dcc1b4c4dc51e73d00f7))
|
||||
* **setup-magento:** extension working dir changed to _ghamagento folder ([#246](https://github.com/graycoreio/github-actions-magento2/issues/246)) ([a729f8b](https://github.com/graycoreio/github-actions-magento2/commit/a729f8b2fda45af7c4a4cd0bbe32bdf5151bf125))
|
||||
* **setup-magento:** mkdir app/etc in extension mode ([#246](https://github.com/graycoreio/github-actions-magento2/issues/246)) ([c53607c](https://github.com/graycoreio/github-actions-magento2/commit/c53607cca85b77c08a9ae826e5f1365f2b7b9ace))
|
||||
* **supported-version:** dynamically append "version" to matrix ([a7e327d](https://github.com/graycoreio/github-actions-magento2/commit/a7e327d44f6dbca270be5f5c5488498f8ba27b2b))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **coding-standard:** use exactly phpcs.xml if exists ([#243](https://github.com/graycoreio/github-actions-magento2/issues/243)) ([a1c6246](https://github.com/graycoreio/github-actions-magento2/commit/a1c6246c7834203379f25acb03ba8ad7ad42c859))
|
||||
* **fix-magento-install:** remove deprecated set-output ([c115395](https://github.com/graycoreio/github-actions-magento2/commit/c115395583913b1beb539aa514a305d8dcbb9364))
|
||||
|
||||
## [7.0.0](https://github.com/graycoreio/github-actions-magento2/compare/v7.0.0-rc.0...v7.0.0) (2026-04-29)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **supported-version:** update for Mage-OS 2.2.2 ([#317](https://github.com/graycoreio/github-actions-magento2/issues/317)) ([bbecc7f](https://github.com/graycoreio/github-actions-magento2/commit/bbecc7f5f9e4ddfdf5b41eb17fac6db1b30b56f0))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **cache-magento:** address `set-output` deprecation ([#231](https://github.com/graycoreio/github-actions-magento2/issues/231)) ([771dd05](https://github.com/graycoreio/github-actions-magento2/commit/771dd054395a0e0a33e2d712d9793ca8322173e9))
|
||||
* **supported-version:** filter uninstallable versions from usable kind ([#319](https://github.com/graycoreio/github-actions-magento2/issues/319)) ([f803617](https://github.com/graycoreio/github-actions-magento2/commit/f8036173e143ab5d13147c136e0d9e0c6bbb829b))
|
||||
|
||||
|
||||
### Miscellaneous Chores
|
||||
|
||||
* promote release 7.0.0 ([90babb1](https://github.com/graycoreio/github-actions-magento2/commit/90babb16bfb28ca1953c95edb2301c9090b52f67))
|
||||
|
||||
## [7.0.0-rc.0](https://github.com/graycoreio/github-actions-magento2/compare/v6.0.0...v7.0.0-rc.0) (2026-04-28)
|
||||
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||

|
||||
[](https://github.com/graycoreio/github-actions-magento2/actions/workflows/_internal-integration.yaml)
|
||||
[](https://github.com/graycoreio/github-actions-magento2/actions/workflows/_internal_check_extension.yaml)
|
||||
[](https://github.com/graycoreio/github-actions-magento2/actions/workflows/_internal-check-store.yaml)
|
||||
|
||||
</div>
|
||||
|
||||
@@ -16,13 +17,19 @@ Opinionated Github Actions and Workflows to make building, testing, and maintain
|
||||
|
||||
## Workflows
|
||||
|
||||
If you are new here, start with a reusable workflow. They are pre-built CI pipelines that you can adopt in minutes — no deep knowledge of the individual actions required. Pick the one that matches your project type and call it from your own workflow file.
|
||||
|
||||
| Workflow Name | Description |
|
||||
| -------------------------------------------------------------------- | ------------------------------------------------------------------------------------ |
|
||||
| [Integration Test](./.github/workflows/integration-README.md) | A Github Workflow that runs the Integration Tests of a Magento Package |
|
||||
| [MageCheck Extension](./.github/workflows/check-extension-README.md) | A Github Workflow that runs various kinds of quality checks for a Magento Extension. |
|
||||
| [Integration Test](./docs/workflows/integration.md) | **Deprecated** — use MageCheck Extension instead. A Github Workflow that runs the Integration Tests of a Magento Package. |
|
||||
| [MageCheck Extension](./docs/workflows/check-extension.md) | A Github Workflow that runs various kinds of quality checks for a Magento Extension. |
|
||||
| [MageCheck Store](./docs/workflows/check-store.md) | A Github Workflow that runs various kinds of quality checks for a Magento Store. |
|
||||
|
||||
## Actions
|
||||
|
||||
If the reusable workflows do not fit your needs, the individual actions are the building blocks they are composed from. Use these when you need full control over your pipeline.
|
||||
|
||||
|
||||
| Action Name | Description |
|
||||
| ------------------------------------------------------------ | ----------------------------------------------------------------------------------------- |
|
||||
| [Fix Magento Install](./fix-magento-install/README.md) | A Github Action that fixes Magento before `composer install` |
|
||||
@@ -33,3 +40,6 @@ Opinionated Github Actions and Workflows to make building, testing, and maintain
|
||||
| [Coding Standard](./coding-standard/README.md) | A Github Action that runs the Magento Coding Standard. |
|
||||
| [Semver Compare](./semver-compare/README.md) | A Github Action that semantically compares two versions |
|
||||
| [Supported Version](./supported-version/README.md) | A Github Action that computes the currently supported Github Actions Matrix for Magento 2 |
|
||||
| [Setup Install](./setup-install/README.md) | A Github Action that runs `bin/magento setup:install` from the supported-version services matrix |
|
||||
| [Setup DI Compile](./setup-di-compile/README.md) | A Github Action that enables all modules and runs `bin/magento setup:di:compile` |
|
||||
| [Sansec eComscan](./sansec-ecomscan/README.md) | A Github Action that runs the Sansec eComscan security scanner. |
|
||||
|
||||
+65
-26
@@ -4,35 +4,74 @@ A Github Action that creates a composer cache for a Magento extension or store.
|
||||
|
||||
## Inputs
|
||||
|
||||
|
||||
See the [action.yml](./action.yml)
|
||||
|
||||
| Input | Description | Required | Default |
|
||||
| ------------------ | -------------------------------------------------------------------------------------- | -------- | ------------ |
|
||||
| composer_cache_key | A key to version the composer cache. Can be incremented if you need to bust the cache. | false | '__mageos' |
|
||||
| Input | Description | Required | Default |
|
||||
| -------------------- | -------------------------------------------------------------------------------------- | -------- | ---------- |
|
||||
| `composer_cache_key` | A key to version the composer cache. Can be incremented if you need to bust the cache. | false | `__mageos` |
|
||||
| `working-directory` | The directory where Magento is installed (location of `vendor/` and `composer.lock`). | false | `.` |
|
||||
| `stamp` | Cache the `vendor/` directory in addition to the Composer download cache. | false | `false` |
|
||||
|
||||
### Usage
|
||||
## Cache keys
|
||||
|
||||
The download cache key has the format:
|
||||
|
||||
```
|
||||
composer | v5.8 | <os> | <composer_cache_key> | <composer-version> | <php-version>
|
||||
```
|
||||
|
||||
When `stamp: true`, the `vendor/` cache key has the format:
|
||||
|
||||
```
|
||||
composer | stamp | v5.8 | <os> | <composer_cache_key> | <composer-version> | <php-version> | <composer.lock-hash>
|
||||
```
|
||||
|
||||
The `composer.lock` hash is derived from `working-directory/composer.lock` using `hashFiles`. The download key also gains the hash suffix when a Magento product package is detected at `working-directory`.
|
||||
|
||||
## Usage
|
||||
|
||||
### Extension (download cache only)
|
||||
|
||||
```yml
|
||||
name: Magento Cache
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
showcase_cache:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: graycoreio/github-actions-magento2/cache-magento@main
|
||||
id: cache-magento
|
||||
|
||||
- run: composer install
|
||||
shell: bash
|
||||
name: Composer install
|
||||
- uses: graycoreio/github-actions-magento2/cache-magento@v8.5.0 # x-release-please-version
|
||||
with:
|
||||
composer_cache_key: ${{ inputs.composer_cache_key }}
|
||||
```
|
||||
|
||||
### Extension or store (download + vendor stamp cache)
|
||||
|
||||
```yml
|
||||
- uses: graycoreio/github-actions-magento2/setup-magento@v8.5.0 # x-release-please-version
|
||||
id: setup-magento
|
||||
with:
|
||||
mode: extension # or store
|
||||
# ...
|
||||
|
||||
- uses: graycoreio/github-actions-magento2/cache-magento@v8.5.0 # x-release-please-version
|
||||
with:
|
||||
composer_cache_key: ${{ inputs.composer_cache_key }}
|
||||
working-directory: ${{ steps.setup-magento.outputs.path }}
|
||||
stamp: true
|
||||
|
||||
- run: composer install
|
||||
working-directory: ${{ steps.setup-magento.outputs.path }}
|
||||
```
|
||||
|
||||
### Stamp Mode
|
||||
|
||||
On a warm cache hit, `composer install` completes in ~0s because `vendor/` is already present — Composer sees everything installed and exits immediately. For a full Magento install this saves 2–5 minutes of package extraction per job.
|
||||
|
||||
The trade-off is size. The `vendor/` directory for a Magento project runs 300–600 MB, so a frequent cache miss means you are consistently paying the upload cost without recouping it on the next run.
|
||||
|
||||
As such, use `stamp: true` when `composer.lock` is stable across most runs — a store on a release branch, or extension CI against a pinned Magento version. Skip it when the lock changes often or when runner storage is constrained.
|
||||
|
||||
> [!WARNING]
|
||||
> **Dependabot / Renovate:** Each time a Dependabot or Renovate PR is merged, the remaining open PRs rebase and each produces a new `composer.lock`. This cascades into a large number of unique cache entries, inflating storage costs without delivering proportional compute savings — because automated PRs are not waiting on fast feedback. The fix is to disable stamp caching for automated dependency PRs entirely:
|
||||
>
|
||||
> ```yml
|
||||
> - uses: graycoreio/github-actions-magento2/cache-magento@v8.5.0 # x-release-please-version
|
||||
> with:
|
||||
> stamp: ${{ github.actor != 'dependabot[bot]' }}
|
||||
> ```
|
||||
>
|
||||
> If you use Renovate, check its bot account name and adjust the condition accordingly. Dependabot PRs will pay full `composer install` time on every run, which is acceptable — nobody is waiting on them.
|
||||
|
||||
@@ -7,11 +7,31 @@ inputs:
|
||||
required: false
|
||||
default: "__mageos"
|
||||
description: A key to version the composer cache. Can be incremented if you need to bust the cache.
|
||||
working-directory:
|
||||
required: false
|
||||
default: "."
|
||||
description: "The working directory where Magento is installed (location of vendor/ and composer.lock)"
|
||||
stamp:
|
||||
required: false
|
||||
default: "false"
|
||||
description: "Cache the vendor/ directory in addition to the Composer download cache"
|
||||
exclude-from-stamp:
|
||||
required: false
|
||||
default: ""
|
||||
description: |
|
||||
Newline-separated list of Composer package names to exclude from the stamp cache (e.g. magento/module-foo).
|
||||
magento/magento2-base and mage-os/magento2-base are always excluded regardless of this input.
|
||||
|
||||
outputs:
|
||||
cache-hit:
|
||||
description: "A boolean value to indicate an exact match was found for the key"
|
||||
value: ${{ steps.cache-magento-cache.outputs.cache-hit }}
|
||||
key:
|
||||
description: "The cache key used for the Composer download cache"
|
||||
value: ${{ steps.cache-magento-keys.outputs.download-key }}
|
||||
stamp-key:
|
||||
description: "The cache key used for the vendor/ stamp cache (only set when stamp: true)"
|
||||
value: ${{ steps.cache-magento-keys.outputs.stamp-key }}
|
||||
|
||||
runs:
|
||||
using: "composite"
|
||||
@@ -22,22 +42,85 @@ runs:
|
||||
run: |
|
||||
echo "dir=$(composer config cache-files-dir --global)" >> $GITHUB_OUTPUT
|
||||
|
||||
- run: echo "::set-output name=version::$(php -v | awk 'NR==1{print $2}')"
|
||||
- run: echo "version=$(php -v | awk 'NR==1{print $2}')" >> "$GITHUB_OUTPUT"
|
||||
shell: bash
|
||||
id: cache-magento-get-php-version
|
||||
|
||||
- run: echo "::set-output name=version::$(composer --version | awk '{print $3}')"
|
||||
- run: echo "version=$(composer --version | awk '{print $3}')" >> "$GITHUB_OUTPUT"
|
||||
shell: bash
|
||||
name: Compute Composer Version
|
||||
id: cache-magento-get-composer-version
|
||||
|
||||
- name: Validate working-directory is inside GITHUB_WORKSPACE
|
||||
if: inputs.stamp == 'true'
|
||||
shell: bash
|
||||
run: |
|
||||
WORKING_DIR="$(realpath '${{ inputs.working-directory }}')"
|
||||
WORKSPACE="$(realpath '${{ github.workspace }}')"
|
||||
if [[ "$WORKING_DIR" != "$WORKSPACE"* ]]; then
|
||||
echo "::error::cache-magento: working-directory '${{ inputs.working-directory }}' resolves outside GITHUB_WORKSPACE. You cannot use the `stamp` cache in folders outside the workspace."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- uses: graycoreio/github-actions-magento2/get-magento-version@main
|
||||
id: cache-magento-get-magento-version
|
||||
with:
|
||||
working-directory: ${{ inputs.working-directory }}
|
||||
|
||||
- name: Validate composer.lock exists for stamp cache (store mode)
|
||||
if: inputs.stamp == 'true' && steps.cache-magento-get-magento-version.outputs.project != ''
|
||||
shell: bash
|
||||
run: |
|
||||
if [[ ! -f "${{ inputs.working-directory }}/composer.lock" ]]; then
|
||||
echo "::error::cache-magento: stamp: true on a store requires a '${{ inputs.working-directory }}/composer.lock' to exist."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Compute cache keys
|
||||
id: cache-magento-keys
|
||||
shell: bash
|
||||
run: |
|
||||
bash "${{ github.action_path }}/compute-cache-keys.sh" \
|
||||
"${{ inputs.composer_cache_key }}" \
|
||||
"${{ runner.os }}" \
|
||||
"${{ steps.cache-magento-get-php-version.outputs.version }}" \
|
||||
"${{ steps.cache-magento-get-composer-version.outputs.version }}" \
|
||||
"${{ steps.cache-magento-get-magento-version.outputs.project }}" \
|
||||
"${{ hashFiles(format('{0}/composer.lock', inputs.working-directory)) }}" \
|
||||
>> $GITHUB_OUTPUT
|
||||
|
||||
- name: "Cache Composer Packages"
|
||||
uses: actions/cache@v5
|
||||
id: cache-magento-cache
|
||||
with:
|
||||
key: "composer | v5.8 | ${{ inputs.composer_cache_key }} | ${{ steps.cache-magento-get-composer-version.outputs.version }} | ${{ steps.cache-magento-get-php-version.outputs.version }}"
|
||||
key: ${{ steps.cache-magento-keys.outputs.download-key }}
|
||||
restore-keys: |
|
||||
${{ steps.cache-magento-keys.outputs.download-restore-key }}
|
||||
path: ${{ steps.cache-magento-composer-cache.outputs.dir }}
|
||||
|
||||
- name: Compute stamp paths
|
||||
id: cache-magento-stamp-paths
|
||||
if: inputs.stamp == 'true'
|
||||
shell: bash
|
||||
env:
|
||||
EXCLUDE_FROM_STAMP: ${{ inputs.exclude-from-stamp }}
|
||||
WORKING_DIR: ${{ inputs.working-directory }}
|
||||
run: |
|
||||
PATHS=$(bash "${{ github.action_path }}/compute-stamp-paths.sh" "$(realpath "$WORKING_DIR")" "$EXCLUDE_FROM_STAMP")
|
||||
{
|
||||
echo "paths<<EOF"
|
||||
echo "$PATHS"
|
||||
echo "EOF"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: "Cache Vendor (Stamp)"
|
||||
uses: actions/cache@v5
|
||||
if: inputs.stamp == 'true'
|
||||
with:
|
||||
key: ${{ steps.cache-magento-keys.outputs.stamp-key }}
|
||||
path: |
|
||||
${{ steps.cache-magento-stamp-paths.outputs.paths }}
|
||||
|
||||
branding:
|
||||
icon: "code"
|
||||
color: "green"
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
#!/usr/bin/env bash
|
||||
# Args: composer_cache_key os php_version composer_version project lock_hash
|
||||
COMPOSER_CACHE_KEY="$1"
|
||||
OS="$2"
|
||||
PHP_VERSION="$3"
|
||||
COMPOSER_VERSION="$4"
|
||||
PROJECT="$5"
|
||||
LOCK_HASH="${6:-}"
|
||||
|
||||
MODE="extension"
|
||||
SUFFIX=""
|
||||
if [ -n "$PROJECT" ]; then
|
||||
MODE="store"
|
||||
[ -n "$LOCK_HASH" ] && SUFFIX=" | $LOCK_HASH"
|
||||
fi
|
||||
|
||||
BASE="composer | v5.8 | ${OS} | ${MODE} | ${COMPOSER_CACHE_KEY} | ${COMPOSER_VERSION} | ${PHP_VERSION}"
|
||||
echo "download-key=${BASE}${SUFFIX}"
|
||||
echo "download-restore-key=${BASE}"
|
||||
echo "stamp-key=composer | stamp | v5.8 | ${OS} | ${MODE} | ${COMPOSER_CACHE_KEY} | ${COMPOSER_VERSION} | ${PHP_VERSION}${SUFFIX}"
|
||||
Executable
+18
@@ -0,0 +1,18 @@
|
||||
#!/usr/bin/env bash
|
||||
# Args: working_directory exclude_from_stamp
|
||||
# working_directory: absolute path (caller is responsible for realpath resolution)
|
||||
# exclude_from_stamp: newline-separated list of composer package names to exclude
|
||||
WORKING_DIR="$1"
|
||||
EXCLUDE_FROM_STAMP="${2:-}"
|
||||
VENDOR="${WORKING_DIR}/vendor"
|
||||
|
||||
PATHS="${VENDOR}/**"$'\n'"!${VENDOR}/**/"$'\n'"!${VENDOR}/magento/magento2-base"$'\n'"!${VENDOR}/magento/magento2-base/**"$'\n'"!${VENDOR}/mage-os/magento2-base"$'\n'"!${VENDOR}/mage-os/magento2-base/**"
|
||||
|
||||
while IFS= read -r pkg; do
|
||||
pkg="${pkg#"${pkg%%[![:space:]]*}"}"
|
||||
pkg="${pkg%"${pkg##*[![:space:]]}"}"
|
||||
[[ -z "$pkg" ]] && continue
|
||||
PATHS="${PATHS}"$'\n'"!${VENDOR}/${pkg}"$'\n'"!${VENDOR}/${pkg}/**"
|
||||
done <<< "$EXCLUDE_FROM_STAMP"
|
||||
|
||||
echo "$PATHS"
|
||||
Generated
+9
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"packages": [
|
||||
{
|
||||
"name": "magento/product-community-edition",
|
||||
"version": "2.4.7"
|
||||
}
|
||||
],
|
||||
"packages-dev": []
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
SCRIPT="$SCRIPT_DIR/compute-cache-keys.sh"
|
||||
SCRIPT_STAMP="$SCRIPT_DIR/compute-stamp-paths.sh"
|
||||
LOCK_HASH=$(sha256sum "$SCRIPT_DIR/fixtures/composer.lock" | cut -d' ' -f1)
|
||||
PASS=0
|
||||
FAIL=0
|
||||
|
||||
assert_eq() {
|
||||
local label="$1" expected="$2" actual="$3"
|
||||
if [ "$expected" = "$actual" ]; then
|
||||
echo "PASS: $label"
|
||||
PASS=$((PASS + 1))
|
||||
else
|
||||
echo "FAIL: $label"
|
||||
echo " expected: $expected"
|
||||
echo " actual: $actual"
|
||||
FAIL=$((FAIL + 1))
|
||||
fi
|
||||
}
|
||||
|
||||
field() {
|
||||
echo "$1" | grep "^${2}=" | cut -d= -f2-
|
||||
}
|
||||
|
||||
# Extension mode: no project resolved, mode derived as "extension", no lock suffix
|
||||
OUT=$(bash "$SCRIPT" "_mageos" "Linux" "8.3.0" "2.2.6" "" "")
|
||||
assert_eq "extension: download-key" \
|
||||
"composer | v5.8 | Linux | extension | _mageos | 2.2.6 | 8.3.0" \
|
||||
"$(field "$OUT" download-key)"
|
||||
assert_eq "extension: download-restore-key" \
|
||||
"composer | v5.8 | Linux | extension | _mageos | 2.2.6 | 8.3.0" \
|
||||
"$(field "$OUT" download-restore-key)"
|
||||
assert_eq "extension: stamp-key" \
|
||||
"composer | stamp | v5.8 | Linux | extension | _mageos | 2.2.6 | 8.3.0" \
|
||||
"$(field "$OUT" stamp-key)"
|
||||
|
||||
# Store mode: project resolved, mode derived as "store", lock hash appended (restore-key drops lock for prefix match)
|
||||
OUT=$(bash "$SCRIPT" "_mageos" "Linux" "8.3.0" "2.2.6" "magento/project-community-edition" "$LOCK_HASH")
|
||||
assert_eq "store: download-key" \
|
||||
"composer | v5.8 | Linux | store | _mageos | 2.2.6 | 8.3.0 | $LOCK_HASH" \
|
||||
"$(field "$OUT" download-key)"
|
||||
assert_eq "store: download-restore-key" \
|
||||
"composer | v5.8 | Linux | store | _mageos | 2.2.6 | 8.3.0" \
|
||||
"$(field "$OUT" download-restore-key)"
|
||||
assert_eq "store: stamp-key" \
|
||||
"composer | stamp | v5.8 | Linux | store | _mageos | 2.2.6 | 8.3.0 | $LOCK_HASH" \
|
||||
"$(field "$OUT" stamp-key)"
|
||||
|
||||
# Store mode without composer.lock (e.g. stamp=false on a store before `composer install`):
|
||||
# lock_hash is empty, so keys must not carry a trailing " | " with an empty hash slot.
|
||||
OUT=$(bash "$SCRIPT" "_mageos" "Linux" "8.3.0" "2.2.6" "magento/project-community-edition" "")
|
||||
assert_eq "store no-lock: download-key" \
|
||||
"composer | v5.8 | Linux | store | _mageos | 2.2.6 | 8.3.0" \
|
||||
"$(field "$OUT" download-key)"
|
||||
assert_eq "store no-lock: download-restore-key" \
|
||||
"composer | v5.8 | Linux | store | _mageos | 2.2.6 | 8.3.0" \
|
||||
"$(field "$OUT" download-restore-key)"
|
||||
assert_eq "store no-lock: stamp-key" \
|
||||
"composer | stamp | v5.8 | Linux | store | _mageos | 2.2.6 | 8.3.0" \
|
||||
"$(field "$OUT" stamp-key)"
|
||||
|
||||
# Custom composer_cache_key, no project resolved
|
||||
OUT=$(bash "$SCRIPT" "custom-v2" "Linux" "8.1.5" "2.4.2" "" "")
|
||||
assert_eq "custom key: download-key" \
|
||||
"composer | v5.8 | Linux | extension | custom-v2 | 2.4.2 | 8.1.5" \
|
||||
"$(field "$OUT" download-key)"
|
||||
assert_eq "custom key: download-restore-key" \
|
||||
"composer | v5.8 | Linux | extension | custom-v2 | 2.4.2 | 8.1.5" \
|
||||
"$(field "$OUT" download-restore-key)"
|
||||
assert_eq "custom key: stamp-key" \
|
||||
"composer | stamp | v5.8 | Linux | extension | custom-v2 | 2.4.2 | 8.1.5" \
|
||||
"$(field "$OUT" stamp-key)"
|
||||
|
||||
# Stamp paths: no excludes — base paths only, with magento2-base always excluded
|
||||
OUT=$(bash "$SCRIPT_STAMP" "/work" "")
|
||||
EXPECTED="/work/vendor/**
|
||||
!/work/vendor/**/
|
||||
!/work/vendor/magento/magento2-base
|
||||
!/work/vendor/magento/magento2-base/**
|
||||
!/work/vendor/mage-os/magento2-base
|
||||
!/work/vendor/mage-os/magento2-base/**"
|
||||
assert_eq "stamp paths: no excludes" "$EXPECTED" "$OUT"
|
||||
|
||||
# Stamp paths: single exclude appended after the always-excluded base entries
|
||||
OUT=$(bash "$SCRIPT_STAMP" "/work" "magento/module-foo")
|
||||
EXPECTED="/work/vendor/**
|
||||
!/work/vendor/**/
|
||||
!/work/vendor/magento/magento2-base
|
||||
!/work/vendor/magento/magento2-base/**
|
||||
!/work/vendor/mage-os/magento2-base
|
||||
!/work/vendor/mage-os/magento2-base/**
|
||||
!/work/vendor/magento/module-foo
|
||||
!/work/vendor/magento/module-foo/**"
|
||||
assert_eq "stamp paths: single exclude" "$EXPECTED" "$OUT"
|
||||
|
||||
# Stamp paths: multiple excludes, with whitespace and blank lines tolerated
|
||||
OUT=$(bash "$SCRIPT_STAMP" "/work" "$(printf 'magento/module-foo\n magento/module-bar \n\nvendor/pkg-baz\n')")
|
||||
EXPECTED="/work/vendor/**
|
||||
!/work/vendor/**/
|
||||
!/work/vendor/magento/magento2-base
|
||||
!/work/vendor/magento/magento2-base/**
|
||||
!/work/vendor/mage-os/magento2-base
|
||||
!/work/vendor/mage-os/magento2-base/**
|
||||
!/work/vendor/magento/module-foo
|
||||
!/work/vendor/magento/module-foo/**
|
||||
!/work/vendor/magento/module-bar
|
||||
!/work/vendor/magento/module-bar/**
|
||||
!/work/vendor/vendor/pkg-baz
|
||||
!/work/vendor/vendor/pkg-baz/**"
|
||||
assert_eq "stamp paths: multiple excludes with whitespace and blank lines" "$EXPECTED" "$OUT"
|
||||
|
||||
echo ""
|
||||
echo "$PASS passed, $FAIL failed"
|
||||
[ "$FAIL" -eq 0 ]
|
||||
@@ -36,7 +36,7 @@ jobs:
|
||||
tools: composer:v2
|
||||
coverage: none
|
||||
|
||||
- uses: graycoreio/github-actions-magento2/coding-standard@main
|
||||
- uses: graycoreio/github-actions-magento2/coding-standard@v8.5.0 # x-release-please-version
|
||||
with:
|
||||
path: app/code # Optional, defaults to .
|
||||
version: 25 # Optional, will use the latest if omitted.
|
||||
|
||||
@@ -52,12 +52,12 @@ runs:
|
||||
fi
|
||||
|
||||
- name: Get Composer Version
|
||||
uses: graycoreio/github-actions-magento2/get-composer-version@v7.0.0-rc.0
|
||||
uses: graycoreio/github-actions-magento2/get-composer-version@main
|
||||
id: get-composer-version
|
||||
if: steps.check-installed.outputs.installed != 'true'
|
||||
|
||||
- name: Check if allow-plugins option is available for this version of composer
|
||||
uses: graycoreio/github-actions-magento2/semver-compare@v7.0.0-rc.0
|
||||
uses: graycoreio/github-actions-magento2/semver-compare@main
|
||||
id: is-allow-plugins-available
|
||||
if: steps.check-installed.outputs.installed != 'true'
|
||||
with:
|
||||
@@ -111,7 +111,7 @@ runs:
|
||||
[ -n "${{ inputs.error_severity }}" ] && FLAGS+=(--error-severity=${{ inputs.error_severity }}) || true
|
||||
|
||||
if [ -f .phpcs.xml ] || [ -f phpcs.xml ] || [ -f .phpcs.xml.dist ] || [ -f phpcs.xml.dist ]; then
|
||||
vendor/bin/phpcs "${FLAGS[@]}" .
|
||||
vendor/bin/phpcs "${FLAGS[@]}"
|
||||
else
|
||||
vendor/bin/phpcs --standard=Magento2 --ignore=*vendor/* "${FLAGS[@]}" .
|
||||
fi
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
# Configure Service Nginx
|
||||
|
||||
A GitHub Action that pushes a Magento-aware nginx configuration into an **already-running nginx service container**, reloads it, and waits for the container's healthcheck to pass.
|
||||
|
||||
The action does **not** start nginx. It assumes the calling workflow declared nginx as a `services:` container (typically alongside `php-fpm`, and other mandatory Magento services).
|
||||
|
||||
The shipped `default.conf` is a thin outer wrapper that defines a `fastcgi_backend` upstream pointing at `php-fpm:9000`, sets `$MAGE_ROOT`, and includes Magento's own `nginx.conf.sample` from your own Magento install. All real routing rules come from Magento's bundled file.
|
||||
|
||||
## When to use this
|
||||
|
||||
Use this action when you have a workflow that:
|
||||
|
||||
1. Boots nginx as `services:` containers with the workspace bind-mounted at `/var/www/html`, and
|
||||
2. Wants those containers to actually serve a Magento store you've already installed into the workspace (e.g. for end-to-end smoke tests, integration tests, or any HTTP-driven check).
|
||||
|
||||
You do **not** need this action if:
|
||||
|
||||
- You're not running nginx at all (unit tests, coding standards, static analysis).
|
||||
- nginx is started by something other than a GitHub Actions `services:` block
|
||||
- You've already configured nginx some other way and don't need a Magento-ready outer config.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- An nginx service container is running on the same Docker host as the runner, with an image matching the `image` input.
|
||||
- A `php-fpm` container, the included `default.conf` will set up a fast-cgi backend to `php-fpm:9000`.
|
||||
- The runner's workspace (`$GITHUB_WORKSPACE`) is bind-mounted into the nginx container at `/var/www/html`.
|
||||
- A Magento install exists at `${{ inputs.magento_path }}` relative to the workspace, with `nginx.conf.sample` present (it ships with Magento by default after `composer install`).
|
||||
|
||||
## Inputs
|
||||
|
||||
| Input | Required | Default | Description |
|
||||
| ------------------------ | -------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `container_id` | Yes | — | The ID of the running nginx service container. Pass `${{ job.services.nginx.id }}` (replace `nginx` with whatever you named the service). |
|
||||
| `magento_path` | No | `.` | Path to the Magento store, relative to the GitHub workspace. Combined with the `/var/www/html` mount prefix to compute the in-container `MAGE_ROOT`. |
|
||||
| `health_timeout_seconds` | No | `10` | How long to wait for nginx to report `healthy` after the config is pushed and the container restarts. |
|
||||
|
||||
## Usage
|
||||
|
||||
```yml
|
||||
jobs:
|
||||
smoke-test:
|
||||
runs-on: ubuntu-latest
|
||||
services:
|
||||
## There are other service requirements for Magento, but this is just for the explanation of this service
|
||||
nginx:
|
||||
image: nginx:1.27-alpine
|
||||
ports: ["80:80"]
|
||||
volumes:
|
||||
- ${{ github.workspace }}:/var/www/html
|
||||
options: --health-cmd "nginx -t" --health-interval=10s --health-retries=3
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: graycoreio/github-actions-magento2/setup-magento@main
|
||||
id: setup-magento
|
||||
with:
|
||||
mode: store
|
||||
- run: composer install
|
||||
working-directory: ${{ steps.setup-magento.outputs.path }}
|
||||
- uses: graycoreio/github-actions-magento2/setup-install@main
|
||||
with:
|
||||
services: ${{ toJSON(matrix.services) }}
|
||||
path: ${{ steps.setup-magento.outputs.path }}
|
||||
container_id: ${{ job.services['php-fpm'].id }}
|
||||
- uses: graycoreio/github-actions-magento2/configure-service-nginx@main
|
||||
with:
|
||||
container_id: ${{ job.services.nginx.id }}
|
||||
magento_path: ${{ inputs.path }}
|
||||
- uses: graycoreio/github-actions-magento2/smoke-test@main
|
||||
with:
|
||||
kind: page
|
||||
```
|
||||
@@ -0,0 +1,67 @@
|
||||
name: "Configure nginx service container"
|
||||
author: "Graycore"
|
||||
description: "Pushes a Magento-aware nginx config into an already-running nginx service container, restarts it, and waits for the container healthcheck to pass."
|
||||
|
||||
inputs:
|
||||
container_id:
|
||||
description: "The ID of the running nginx service container. Pass the value of `job.services.nginx.id` (replace `nginx` with whatever you named the service in `services:`)."
|
||||
required: true
|
||||
magento_path:
|
||||
description: "Path to the Magento store, relative to the GitHub workspace. The workspace is mounted at /var/www/html in both the nginx and php-fpm service containers, so this is combined with that prefix to compute MAGE_ROOT."
|
||||
required: false
|
||||
default: "."
|
||||
health_timeout_seconds:
|
||||
description: "How long to wait for nginx to become healthy after the config is pushed and the container is restarted."
|
||||
required: false
|
||||
default: "10"
|
||||
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- name: Push nginx config
|
||||
shell: bash
|
||||
env:
|
||||
NGINX_CID: ${{ inputs.container_id }}
|
||||
ACTION_PATH: ${{ github.action_path }}
|
||||
MAGENTO_PATH: ${{ inputs.magento_path }}
|
||||
run: |
|
||||
case "$MAGENTO_PATH" in
|
||||
""|".") MAGE_ROOT="/var/www/html" ;;
|
||||
/*) MAGE_ROOT="$MAGENTO_PATH" ;;
|
||||
*) MAGE_ROOT="/var/www/html/$MAGENTO_PATH" ;;
|
||||
esac
|
||||
MAGE_ROOT="${MAGE_ROOT%/}"
|
||||
|
||||
sed "s|__MAGE_ROOT__|$MAGE_ROOT|g" "$ACTION_PATH/conf.d/default.conf" > /tmp/default.conf
|
||||
|
||||
docker cp /tmp/default.conf "$NGINX_CID:/etc/nginx/conf.d/default.conf"
|
||||
|
||||
echo "--- default.conf in container ---"
|
||||
docker exec "$NGINX_CID" cat /etc/nginx/conf.d/default.conf
|
||||
docker exec "$NGINX_CID" nginx -t
|
||||
|
||||
- name: Restart nginx and wait for healthy
|
||||
shell: bash
|
||||
env:
|
||||
NGINX_CID: ${{ inputs.container_id }}
|
||||
TIMEOUT: ${{ inputs.health_timeout_seconds }}
|
||||
run: |
|
||||
docker restart "$NGINX_CID"
|
||||
deadline=$(( $(date +%s) + TIMEOUT ))
|
||||
last=""
|
||||
while [ "$(date +%s)" -lt "$deadline" ]; do
|
||||
last=$(docker inspect --format '{{.State.Health.Status}}' "$NGINX_CID" 2>/dev/null || echo "")
|
||||
running=$(docker inspect --format '{{.State.Running}}' "$NGINX_CID" 2>/dev/null || echo "")
|
||||
echo "running=$running health=$last"
|
||||
if [ "$last" = "healthy" ]; then
|
||||
exit 0
|
||||
fi
|
||||
sleep 2
|
||||
done
|
||||
echo "nginx did not reach healthy state within ${TIMEOUT}s (last=$last)"
|
||||
docker logs "$NGINX_CID" 2>&1 | tail -80 || true
|
||||
exit 1
|
||||
|
||||
branding:
|
||||
icon: "code"
|
||||
color: "green"
|
||||
@@ -0,0 +1,10 @@
|
||||
upstream fastcgi_backend {
|
||||
server php-fpm:9000;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 80 default_server;
|
||||
server_name _;
|
||||
set $MAGE_ROOT __MAGE_ROOT__;
|
||||
include __MAGE_ROOT__/nginx[.]conf*;
|
||||
}
|
||||
@@ -4,7 +4,7 @@ A Github Workflow that runs various kinds of quality checks for a Magento Extens
|
||||
|
||||
## Inputs
|
||||
|
||||
See the [check-extension.yaml](./check-extension.yaml)
|
||||
See the [check-extension.yaml](../../.github/workflows/check-extension.yaml)
|
||||
|
||||
| Input | Description | Required | Default |
|
||||
| ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------- | -------- | --------------------------- |
|
||||
@@ -18,6 +18,23 @@ See the [check-extension.yaml](./check-extension.yaml)
|
||||
|
||||
The Magento matrix format outlined by the [supported versions action.](https://github.com/graycoreio/github-actions-magento2/tree/main/supported-version/supported.json)
|
||||
|
||||
## Configuration
|
||||
|
||||
Each check can be toggled on/off through an optional `.github/check-extension.json` file in the repo that calls this workflow.
|
||||
|
||||
You can learn more about this file here in the [`resolve-check-config` action.](../../resolve-check-config/README.md):
|
||||
|
||||
Reference the published JSON Schema with `$schema` to get autocompletion and inline validation in editors that support it — see [`check-extension.schema.json`](../../resolve-check-config/check-extension.schema.json):
|
||||
|
||||
```json
|
||||
{
|
||||
"$schema": "https://raw.githubusercontent.com/graycoreio/github-actions-magento2/main/resolve-check-config/check-extension.schema.json",
|
||||
"jobs": {
|
||||
"integration_test": false
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
```yml
|
||||
@@ -38,12 +55,12 @@ jobs:
|
||||
matrix: ${{ steps.supported-version.outputs.matrix }}
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: graycoreio/github-actions-magento2/supported-version@main
|
||||
- uses: graycoreio/github-actions-magento2/supported-version@v8.5.0 # x-release-please-version
|
||||
id: supported-version
|
||||
- run: echo ${{ steps.supported-version.outputs.matrix }}
|
||||
check-extension:
|
||||
needs: compute_matrix
|
||||
uses: graycoreio/github-actions-magento2/.github/workflows/check-extension.yaml@main
|
||||
uses: graycoreio/github-actions-magento2/.github/workflows/check-extension.yaml@v8.5.0 # x-release-please-version
|
||||
with:
|
||||
matrix: ${{ needs.compute_matrix.outputs.matrix }}
|
||||
```
|
||||
@@ -0,0 +1,76 @@
|
||||
# MageCheck Store
|
||||
|
||||
A Github Workflow that runs various kinds of quality checks for a Magento Store.
|
||||
|
||||
Unlike [MageCheck Extension](./check-extension.md), this workflow automatically detects the Magento version from your store's `composer.lock` — no matrix computation required in the calling workflow.
|
||||
|
||||
## Inputs
|
||||
|
||||
See the [check-store.yaml](../../.github/workflows/check-store.yaml)
|
||||
|
||||
| Input | Description | Required | Default |
|
||||
| ------------------- | -------------------------------------------------------------------------------------- | -------- | --------- |
|
||||
| path | The folder of the Magento store that you are testing | false | . |
|
||||
| composer_cache_key | A key to version the composer cache. Can be incremented if you need to bust the cache. | false | \_mageos |
|
||||
| store_artifact_name | If provided, download store files from this artifact instead of using actions/checkout | false | "" |
|
||||
|
||||
## Secrets
|
||||
|
||||
| Input | Description | Required | Default |
|
||||
| ------------- | --------------------------------------------------------------------------------------------------------------------------------------- | -------- | ------- |
|
||||
| composer_auth | Your composer credentials (typically a stringified json object of the contents of your auth.json) | false | NULL |
|
||||
|
||||
## Checks Run
|
||||
|
||||
- **Unit Tests** — runs PHPUnit against custom code in `app/code`. Skipped automatically if no test files are found.
|
||||
- **Coding Standard** — runs the Magento Coding Standard against `app/code`. Uses your `phpcs.xml` (or `.phpcs.xml`, `phpcs.xml.dist`, `.phpcs.xml.dist`) if one exists, otherwise a sensible default is generated.
|
||||
- **Smoke Test** — boots your store against the supported-version service set (mysql, search, queue, cache, nginx, php-fpm) and runs the smoke probes against it.
|
||||
|
||||
## Configuration
|
||||
|
||||
Each check can be toggled on/off through an optional `.github/check-store.json` file in the repo that calls this workflow.
|
||||
|
||||
You can learn more about this file here in the [`resolve-check-config` action.](../../resolve-check-config/README.md):
|
||||
|
||||
Reference the published JSON Schema with `$schema` to get autocompletion and inline validation in editors that support it — see [`check-store.schema.json`](../../resolve-check-config/check-store.schema.json):
|
||||
|
||||
```json
|
||||
{
|
||||
"$schema": "https://raw.githubusercontent.com/graycoreio/github-actions-magento2/main/resolve-check-config/check-store.schema.json",
|
||||
"jobs": {
|
||||
"coding-standard": false
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
```yml
|
||||
name: Check Store
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
check-store:
|
||||
uses: graycoreio/github-actions-magento2/.github/workflows/check-store.yaml@v8.5.0 # x-release-please-version
|
||||
secrets:
|
||||
composer_auth: ${{ secrets.COMPOSER_AUTH }}
|
||||
```
|
||||
|
||||
### Usage with a store artifact
|
||||
|
||||
If your pipeline builds or prepares the store in a prior job and uploads it as an artifact, you can pass the artifact name instead of relying on `actions/checkout`:
|
||||
|
||||
```yml
|
||||
jobs:
|
||||
check-store:
|
||||
uses: graycoreio/github-actions-magento2/.github/workflows/check-store.yaml@v8.5.0 # x-release-please-version
|
||||
secrets:
|
||||
composer_auth: ${{ secrets.COMPOSER_AUTH }}
|
||||
```
|
||||
@@ -1,10 +1,12 @@
|
||||
# Integration Tests for a Magento Package
|
||||
|
||||
> **Deprecated** — use [MageCheck Extension](./check-extension.md) instead. Scheduled for removal in v10.
|
||||
|
||||
A Github Workflow that runs the Integration Tests of a Magento Package
|
||||
|
||||
## Inputs
|
||||
|
||||
See the [integration.yaml](./integration.yaml)
|
||||
See the [integration.yaml](../../.github/workflows/integration.yaml)
|
||||
|
||||
| Input | Description | Required | Default |
|
||||
| ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------- | -------- | ----------------------------- |
|
||||
@@ -48,13 +50,13 @@ jobs:
|
||||
matrix: ${{ steps.supported-version.outputs.matrix }}
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: graycoreio/github-actions-magento2/supported-version@main
|
||||
- uses: graycoreio/github-actions-magento2/supported-version@v8.5.0 # x-release-please-version
|
||||
with:
|
||||
include_services: true
|
||||
id: supported-version
|
||||
integration-workflow:
|
||||
needs: compute_matrix
|
||||
uses: graycoreio/github-actions-magento2/.github/workflows/integration.yaml@main
|
||||
uses: graycoreio/github-actions-magento2/.github/workflows/integration.yaml@v8.5.0 # x-release-please-version
|
||||
with:
|
||||
package_name: my-vendor/package
|
||||
matrix: ${{ needs.compute_matrix.outputs.matrix }}
|
||||
@@ -26,7 +26,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: graycoreio/github-actions-magento2/fix-magento-install@main
|
||||
- uses: graycoreio/github-actions-magento2/fix-magento-install@v8.5.0 # x-release-please-version
|
||||
with:
|
||||
magento_directory: path/to/magento
|
||||
```
|
||||
|
||||
@@ -9,12 +9,12 @@ inputs:
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- uses: graycoreio/github-actions-magento2/get-magento-version@v7.0.0-rc.0
|
||||
- uses: graycoreio/github-actions-magento2/get-magento-version@main
|
||||
id: init-magento-get-magento-version
|
||||
with:
|
||||
working-directory: ${{ inputs.magento_directory }}
|
||||
|
||||
- run: echo "::set-output name=version::$(composer --version | awk '{print $3}')"
|
||||
- run: echo "version=$(composer --version | awk '{print $3}')" >> $GITHUB_OUTPUT
|
||||
shell: bash
|
||||
name: Compute Composer Version
|
||||
id: init-magento-get-composer-version
|
||||
|
||||
@@ -25,7 +25,7 @@ jobs:
|
||||
name: A job to compute an installed Composer version.
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: graycoreio/github-actions-magento2/get-composer-version@main
|
||||
- uses: graycoreio/github-actions-magento2/get-composer-version@v8.5.0 # x-release-please-version
|
||||
id: get-composer-version
|
||||
- run: echo version ${{ steps.get-composer-version.outputs.version }}
|
||||
shell: bash
|
||||
|
||||
@@ -25,7 +25,7 @@ jobs:
|
||||
name: A job to compute an installed Magento version.
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: graycoreio/github-actions-magento2/get-magento-version@main
|
||||
- uses: graycoreio/github-actions-magento2/get-magento-version@v8.5.0 # x-release-please-version
|
||||
id: get-magento-version
|
||||
- run: echo version ${{ steps.get-magento-version.outputs.version }}
|
||||
shell: bash
|
||||
|
||||
@@ -2,29 +2,30 @@ name: "Get Magento Version"
|
||||
author: "Graycore"
|
||||
description: " A Github Action that determines the currently installed version of Magento"
|
||||
|
||||
inputs:
|
||||
working-directory:
|
||||
inputs:
|
||||
working-directory:
|
||||
default: $GITHUB_WORKSPACE
|
||||
description: "The current working directory of the action"
|
||||
required: false
|
||||
|
||||
outputs:
|
||||
version: # id of output
|
||||
description: 'The determined version of Magento'
|
||||
version:
|
||||
description: 'The resolved Magento version (e.g. 2.4.6-p14)'
|
||||
value: ${{ steps.get-magento-version.outputs.version }}
|
||||
project:
|
||||
description: 'The Magento project package name (e.g. magento/project-community-edition)'
|
||||
value: ${{ steps.get-magento-version.outputs.project }}
|
||||
supported_version_project:
|
||||
description: 'The `project` value to pass to the supported-version action (e.g. `magento-open-source`, `mage-os`, `mage-os-minimal`). Empty when no Magento project is detected.'
|
||||
value: ${{ steps.get-magento-version.outputs.supported_version_project }}
|
||||
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- run: |
|
||||
echo "version=$(cat composer.json | jq '.require
|
||||
| with_entries( select(.key == "magento/product-community-edition" or .key == "magento/product-enterprise-edition") )
|
||||
| to_entries
|
||||
| .[0].value')" >> $GITHUB_OUTPUT
|
||||
shell: bash
|
||||
working-directory: ${{ inputs.working-directory }}
|
||||
name: Compute Installed Magento version
|
||||
- name: Compute Installed Magento version
|
||||
id: get-magento-version
|
||||
shell: bash
|
||||
run: bash "${{ github.action_path }}/get-magento-version.sh" "${{ inputs.working-directory }}" >> $GITHUB_OUTPUT
|
||||
|
||||
branding:
|
||||
icon: "code"
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"packages": [
|
||||
{
|
||||
"name": "magento/product-enterprise-edition",
|
||||
"version": "2.4.7-p1"
|
||||
}
|
||||
],
|
||||
"packages-dev": []
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"name": "vendor/module",
|
||||
"type": "magento2-module",
|
||||
"require": {}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"packages": [
|
||||
{
|
||||
"name": "mage-os/product-minimal-edition",
|
||||
"version": "3.0.0"
|
||||
}
|
||||
],
|
||||
"packages-dev": []
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"packages": [
|
||||
{
|
||||
"name": "mage-os/product-community-edition",
|
||||
"version": "1.0.0"
|
||||
}
|
||||
],
|
||||
"packages-dev": []
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"require": {
|
||||
"magento/product-community-edition": "2.4.6-p1"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"packages": [
|
||||
{
|
||||
"name": "magento/product-community-edition",
|
||||
"version": "2.4.7"
|
||||
}
|
||||
],
|
||||
"packages-dev": []
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"packages": [
|
||||
{
|
||||
"name": "magento/product-community-edition",
|
||||
"version": "v2.4.6"
|
||||
}
|
||||
],
|
||||
"packages-dev": []
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
#!/usr/bin/env bash
|
||||
set -uo pipefail
|
||||
|
||||
WORKING_DIR="${1:-.}"
|
||||
PATTERN="magento/product-(community|enterprise)-edition|mage-os/product-(community|minimal)-edition"
|
||||
|
||||
cd "$WORKING_DIR"
|
||||
|
||||
if [ -f composer.lock ]; then
|
||||
RESULT=$(jq -r --arg p "$PATTERN" '.packages[] | select(.name | test($p)) | "\(.name) \(.version)"' composer.lock | head -1)
|
||||
elif [ -f composer.json ]; then
|
||||
RESULT=$(jq -r --arg p "$PATTERN" '.require | to_entries[] | select(.key | test($p)) | "\(.key) \(.value)"' composer.json | head -1)
|
||||
fi
|
||||
|
||||
PRODUCT=$(echo "${RESULT:-}" | awk '{print $1}')
|
||||
VERSION=$(echo "${RESULT:-}" | awk '{print $2}' | sed 's/^v//')
|
||||
PROJECT=$(echo "$PRODUCT" | sed 's/product-/project-/')
|
||||
|
||||
case "$PROJECT" in
|
||||
magento/*) SUPPORTED_VERSION_PROJECT="magento-open-source" ;;
|
||||
mage-os/project-community-edition) SUPPORTED_VERSION_PROJECT="mage-os" ;;
|
||||
mage-os/project-minimal-edition) SUPPORTED_VERSION_PROJECT="mage-os-minimal" ;;
|
||||
*) SUPPORTED_VERSION_PROJECT="" ;;
|
||||
esac
|
||||
|
||||
echo "version=\"$VERSION\""
|
||||
echo "project=$PROJECT"
|
||||
echo "supported_version_project=$SUPPORTED_VERSION_PROJECT"
|
||||
@@ -0,0 +1,66 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
SCRIPT="$SCRIPT_DIR/get-magento-version.sh"
|
||||
FIXTURES="$SCRIPT_DIR/fixtures"
|
||||
PASS=0
|
||||
FAIL=0
|
||||
|
||||
assert_eq() {
|
||||
local label="$1" expected="$2" actual="$3"
|
||||
if [ "$expected" = "$actual" ]; then
|
||||
echo "PASS: $label"
|
||||
PASS=$((PASS + 1))
|
||||
else
|
||||
echo "FAIL: $label"
|
||||
echo " expected: $expected"
|
||||
echo " actual: $actual"
|
||||
FAIL=$((FAIL + 1))
|
||||
fi
|
||||
}
|
||||
|
||||
field() {
|
||||
echo "$1" | grep "^${2}=" | cut -d= -f2-
|
||||
}
|
||||
|
||||
OUT=$(bash "$SCRIPT" "$FIXTURES/store-lock")
|
||||
assert_eq "store lock: version" '"2.4.7"' "$(field "$OUT" version)"
|
||||
assert_eq "store lock: project" "magento/project-community-edition" "$(field "$OUT" project)"
|
||||
assert_eq "store lock: supported_version_project" "magento-open-source" "$(field "$OUT" supported_version_project)"
|
||||
|
||||
OUT=$(bash "$SCRIPT" "$FIXTURES/store-v-prefix")
|
||||
assert_eq "store v-prefix: version" '"2.4.6"' "$(field "$OUT" version)"
|
||||
|
||||
OUT=$(bash "$SCRIPT" "$FIXTURES/enterprise")
|
||||
assert_eq "enterprise: version" '"2.4.7-p1"' "$(field "$OUT" version)"
|
||||
assert_eq "enterprise: project" "magento/project-enterprise-edition" "$(field "$OUT" project)"
|
||||
assert_eq "enterprise: supported_version_project" "magento-open-source" "$(field "$OUT" supported_version_project)"
|
||||
|
||||
OUT=$(bash "$SCRIPT" "$FIXTURES/mage-os")
|
||||
assert_eq "mage-os: version" '"1.0.0"' "$(field "$OUT" version)"
|
||||
assert_eq "mage-os: project" "mage-os/project-community-edition" "$(field "$OUT" project)"
|
||||
assert_eq "mage-os: supported_version_project" "mage-os" "$(field "$OUT" supported_version_project)"
|
||||
|
||||
OUT=$(bash "$SCRIPT" "$FIXTURES/mage-os-minimal")
|
||||
assert_eq "mage-os-minimal: version" '"3.0.0"' "$(field "$OUT" version)"
|
||||
assert_eq "mage-os-minimal: project" "mage-os/project-minimal-edition" "$(field "$OUT" project)"
|
||||
assert_eq "mage-os-minimal: supported_version_project" "mage-os-minimal" "$(field "$OUT" supported_version_project)"
|
||||
|
||||
OUT=$(bash "$SCRIPT" "$FIXTURES/store-json")
|
||||
assert_eq "store json: version" '"2.4.6-p1"' "$(field "$OUT" version)"
|
||||
assert_eq "store json: project" "magento/project-community-edition" "$(field "$OUT" project)"
|
||||
assert_eq "store json: supported_version_project" "magento-open-source" "$(field "$OUT" supported_version_project)"
|
||||
|
||||
OUT=$(bash "$SCRIPT" "$FIXTURES/extension")
|
||||
assert_eq "extension: version" '""' "$(field "$OUT" version)"
|
||||
assert_eq "extension: project" "" "$(field "$OUT" project)"
|
||||
assert_eq "extension: supported_version_project" "" "$(field "$OUT" supported_version_project)"
|
||||
|
||||
OUT=$(bash "$SCRIPT" "$FIXTURES/empty")
|
||||
assert_eq "empty dir: version" '""' "$(field "$OUT" version)"
|
||||
assert_eq "empty dir: project" "" "$(field "$OUT" project)"
|
||||
assert_eq "empty dir: supported_version_project" "" "$(field "$OUT" supported_version_project)"
|
||||
|
||||
echo ""
|
||||
echo "$PASS passed, $FAIL failed"
|
||||
[ "$FAIL" -eq 0 ]
|
||||
Generated
+23
-7
@@ -1,15 +1,16 @@
|
||||
{
|
||||
"name": "@graycoreio/github-actions-magento2",
|
||||
"version": "7.0.0-rc.0",
|
||||
"version": "8.5.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@graycoreio/github-actions-magento2",
|
||||
"version": "7.0.0-rc.0",
|
||||
"version": "8.5.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@actions/core": "^1.11.1"
|
||||
"@actions/core": "^1.11.1",
|
||||
"@actions/exec": "^3.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jest": "^29.5.14",
|
||||
@@ -33,7 +34,7 @@
|
||||
"@actions/http-client": "^2.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@actions/exec": {
|
||||
"node_modules/@actions/core/node_modules/@actions/exec": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@actions/exec/-/exec-1.1.1.tgz",
|
||||
"integrity": "sha512-+sCcHHbVdk93a0XT19ECtO/gIXoxvdsgQLzb2fE2/5sIZmWQuluYyjPQtrtTHdU1YzTZ7bAPN4sITq2xi1679w==",
|
||||
@@ -42,6 +43,21 @@
|
||||
"@actions/io": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@actions/core/node_modules/@actions/io": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@actions/io/-/io-1.1.3.tgz",
|
||||
"integrity": "sha512-wi9JjgKLYS7U/z8PPbco+PvTb/nRWjeoFlJ1Qer83k/3C5PHQi28hiVdeE2kHXmIL99mQFawx8qt/JPjZilJ8Q==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@actions/exec": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@actions/exec/-/exec-3.0.0.tgz",
|
||||
"integrity": "sha512-6xH/puSoNBXb72VPlZVm7vQ+svQpFyA96qdDBvhB8eNZOE8LtPf9L4oAsfzK/crCL8YZ+19fKYVnM63Sl+Xzlw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@actions/io": "^3.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@actions/http-client": {
|
||||
"version": "2.2.3",
|
||||
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-2.2.3.tgz",
|
||||
@@ -53,9 +69,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@actions/io": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@actions/io/-/io-1.1.3.tgz",
|
||||
"integrity": "sha512-wi9JjgKLYS7U/z8PPbco+PvTb/nRWjeoFlJ1Qer83k/3C5PHQi28hiVdeE2kHXmIL99mQFawx8qt/JPjZilJ8Q==",
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@actions/io/-/io-3.0.2.tgz",
|
||||
"integrity": "sha512-nRBchcMM+QK1pdjO7/idu86rbJI5YHUKCvKs0KxnSYbVe3F51UfGxuZX4Qy/fWlp6l7gWFwIkrOzN+oUK03kfw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@babel/code-frame": {
|
||||
|
||||
+4
-3
@@ -1,9 +1,9 @@
|
||||
{
|
||||
"name": "@graycoreio/github-actions-magento2",
|
||||
"version": "7.0.0-rc.0",
|
||||
"version": "8.5.0",
|
||||
"description": "Github Actions for Magento 2",
|
||||
"scripts": {
|
||||
"test": "cd supported-version && npm run test && cd -",
|
||||
"test": "cd supported-version && npm run test && cd - && cd setup-install && npm run test && cd -",
|
||||
"release": "standard-version"
|
||||
},
|
||||
"private": true,
|
||||
@@ -18,7 +18,8 @@
|
||||
},
|
||||
"homepage": "https://github.com/graycoreio/github-actions-magento2#readme",
|
||||
"dependencies": {
|
||||
"@actions/core": "^1.11.1"
|
||||
"@actions/core": "^1.11.1",
|
||||
"@actions/exec": "^3.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jest": "^29.5.14",
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"bump-minor-pre-major": true,
|
||||
"bump-patch-for-minor-pre-major": true,
|
||||
"draft-pull-request": true,
|
||||
"prerelease": false,
|
||||
"include-component-in-tag": false,
|
||||
"include-v-in-tag": true,
|
||||
"pull-request-title-pattern": "chore: release ${version}",
|
||||
"packages": {
|
||||
".": {
|
||||
"release-type": "node",
|
||||
"versioning": "prerelease",
|
||||
"extra-files": [
|
||||
{ "type": "generic", "path": "*/README.md", "glob": true },
|
||||
{ "type": "generic", "path": "docs/workflows/*.md", "glob": true }
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,12 +3,17 @@
|
||||
"bump-patch-for-minor-pre-major": true,
|
||||
"draft-pull-request": true,
|
||||
"prerelease": true,
|
||||
"prerelease-type": "rc.0",
|
||||
"include-component-in-tag": false,
|
||||
"include-v-in-tag": true,
|
||||
"pull-request-title-pattern": "chore: release ${version}",
|
||||
"packages": {
|
||||
".": {
|
||||
"release-type": "node"
|
||||
"release-type": "node",
|
||||
"extra-files": [
|
||||
{ "type": "generic", "path": "*/README.md", "glob": true },
|
||||
{ "type": "generic", "path": "docs/workflows/*.md", "glob": true }
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"bump-minor-pre-major": true,
|
||||
"bump-patch-for-minor-pre-major": true,
|
||||
"draft-pull-request": true,
|
||||
"prerelease": true,
|
||||
"prerelease-type": "rc.0",
|
||||
"include-component-in-tag": false,
|
||||
"include-v-in-tag": true,
|
||||
"pull-request-title-pattern": "chore: release ${version}",
|
||||
"packages": {
|
||||
".": {
|
||||
"release-type": "node",
|
||||
"versioning": "prerelease"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
# "Resolve Check Config" Action
|
||||
|
||||
Reads `.github/check-<kind>.json` (or a path you specify), validates job names against the known list for the selected workflow kind, and emits a per-job filtered version of the `supported-version` matrix. Each job in the output carries an `enabled` flag and its own `matrix`, where every entry's `services` map has been narrowed to the tiers that job actually needs. Consumers gate each job with `fromJSON(...)['<job>'].enabled != false` and feed `fromJSON(...)['<job>'].matrix` into `strategy.matrix`.
|
||||
|
||||
A missing config file is fine — every known job is emitted with its default tier list.
|
||||
|
||||
## Schemas
|
||||
|
||||
Reference the published JSON Schema from your config's `$schema` key for autocompletion and inline validation in editors that support it:
|
||||
|
||||
- [`check-store.schema.json`](./check-store.schema.json) — config for the [MageCheck Store](../docs/workflows/check-store.md) workflow
|
||||
- [`check-extension.schema.json`](./check-extension.schema.json) — config for the [MageCheck Extension](../docs/workflows/check-extension.md) workflow
|
||||
|
||||
## Inputs
|
||||
|
||||
| Input | Description | Required | Default |
|
||||
|---------------|--------------------------------------------------------------------------------------------------------------------------------------------------------|----------|-------------------------------|
|
||||
| `kind` | Which reusable workflow this config belongs to: `store` or `extension`. Selects the default `config_path`, the known-job list, and the per-job tier defaults. | true | |
|
||||
| `matrix` | The matrix JSON emitted by the `supported-version` action. Each entry's `services` map is filtered per-job based on the resolved tier list. | true | |
|
||||
| `config_path` | Path to the check-config JSON file, relative to the runner workspace. | false | `.github/check-<kind>.json` |
|
||||
|
||||
## Usage
|
||||
|
||||
```yml
|
||||
jobs:
|
||||
compute_matrix:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
resolved: ${{ steps.resolve.outputs.resolved }}
|
||||
steps:
|
||||
- uses: graycoreio/github-actions-magento2/supported-version@v8.5.0 # x-release-please-version
|
||||
id: supported-version
|
||||
with:
|
||||
kind: currently-supported
|
||||
|
||||
- uses: graycoreio/github-actions-magento2/resolve-check-config@v8.5.0 # x-release-please-version
|
||||
id: resolve
|
||||
with:
|
||||
kind: store
|
||||
matrix: ${{ steps.supported-version.outputs.matrix }}
|
||||
|
||||
smoke-test:
|
||||
runs-on: ${{ matrix.os }}
|
||||
needs: compute_matrix
|
||||
if: ${{ fromJSON(needs.compute_matrix.outputs.resolved)['smoke-test'].enabled != false }}
|
||||
services: ${{ matrix.services }}
|
||||
strategy:
|
||||
matrix: ${{ fromJSON(needs.compute_matrix.outputs.resolved)['smoke-test'].matrix }}
|
||||
steps:
|
||||
- run: echo "running with ${{ toJSON(matrix.services) }}"
|
||||
```
|
||||
|
||||
Example `.github/check-store.json` for opting out of a specific job:
|
||||
|
||||
```json
|
||||
{
|
||||
"$schema": "https://raw.githubusercontent.com/graycoreio/github-actions-magento2/main/resolve-check-config/check-store.schema.json",
|
||||
"jobs": {
|
||||
"coding-standard": false
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,27 @@
|
||||
name: "Resolve check config"
|
||||
author: "Graycore"
|
||||
description: "Reads .github/check-<kind>.json (or a path you specify), validates job names against the known list for that workflow kind, and emits a per-job filtered version of the supported-version matrix. Missing config file is treated as 'all jobs enabled with their default tier list.'"
|
||||
|
||||
inputs:
|
||||
kind:
|
||||
required: true
|
||||
description: "Which reusable workflow this config belongs to: `store` or `extension`. Selects the default `config_path`, the known-job list used for validation, and the per-job default tier list."
|
||||
matrix:
|
||||
required: true
|
||||
description: "The matrix JSON emitted by the `supported-version` action. Each entry's `services` map is filtered per-job based on the resolved tier list and embedded in the per-job `matrix` output."
|
||||
config_path:
|
||||
required: false
|
||||
default: ""
|
||||
description: "Path to the check-config JSON file, relative to the runner workspace. Defaults to `.github/check-<kind>.json`. Missing file is fine — every known job is emitted with its default tier list."
|
||||
|
||||
outputs:
|
||||
resolved:
|
||||
description: "The per-job resolved configuration as a JSON object. Each top-level key is a known job name for the selected kind; values are objects with `enabled` (boolean) and `matrix` (a copy of the supported-version matrix where every entry's `services` is filtered to the tiers the job needs). Consumers default-enable omitted jobs via `fromJSON(...)['<job>'].enabled != false` and use `fromJSON(...)['<job>'].matrix` for `strategy.matrix`."
|
||||
|
||||
runs:
|
||||
using: "node24"
|
||||
main: "dist/index.js"
|
||||
|
||||
branding:
|
||||
icon: "check-square"
|
||||
color: "green"
|
||||
@@ -0,0 +1,49 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://raw.githubusercontent.com/graycoreio/github-actions-magento2/main/resolve-check-config/check-extension.schema.json",
|
||||
"title": "graycoreio check-extension config",
|
||||
"description": "Configuration consumed by the check-extension reusable workflow. Per-job toggles and settings live under `jobs`. Top-level remains open for future global keys.",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"jobs": {
|
||||
"type": "object",
|
||||
"description": "Per-job configuration. Each key is a job name declared by check-extension; unknown keys are rejected.",
|
||||
"properties": {
|
||||
"unit-test-extension": { "$ref": "#/$defs/jobConfig" },
|
||||
"compile-extension": { "$ref": "#/$defs/jobConfig" },
|
||||
"coding-standard": { "$ref": "#/$defs/jobConfig" },
|
||||
"integration_test": { "$ref": "#/$defs/jobConfig" }
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"additionalProperties": true,
|
||||
"$defs": {
|
||||
"jobConfig": {
|
||||
"description": "How a single job should be configured. Boolean form is shorthand for { enabled: <bool> }; object form allows extra per-job keys.",
|
||||
"oneOf": [
|
||||
{ "type": "boolean" },
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"enabled": {
|
||||
"type": "boolean",
|
||||
"description": "Whether the job should run. Defaults to true when the key is present.",
|
||||
"default": true
|
||||
},
|
||||
"services": {
|
||||
"type": "array",
|
||||
"description": "Tier names this job needs as GitHub Actions service containers. mysql is always implicit.",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"enum": ["search", "queue", "cache", "web"]
|
||||
},
|
||||
"uniqueItems": true
|
||||
}
|
||||
},
|
||||
"additionalProperties": true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://raw.githubusercontent.com/graycoreio/github-actions-magento2/main/resolve-check-config/check-store.schema.json",
|
||||
"title": "graycoreio check-store config",
|
||||
"description": "Configuration consumed by the check-store reusable workflow. Per-job toggles and settings live under `jobs`. Top-level remains open for future global keys.",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"jobs": {
|
||||
"type": "object",
|
||||
"description": "Per-job configuration. Each key is a job name declared by check-store; unknown keys are rejected.",
|
||||
"properties": {
|
||||
"unit-test": { "$ref": "#/$defs/jobConfig" },
|
||||
"coding-standard": { "$ref": "#/$defs/jobConfig" },
|
||||
"smoke-test": { "$ref": "#/$defs/smokeJobConfig" }
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"additionalProperties": true,
|
||||
"$defs": {
|
||||
"jobConfig": {
|
||||
"description": "How a single job should be configured. Boolean form is shorthand for { enabled: <bool> }; object form allows extra per-job keys.",
|
||||
"oneOf": [
|
||||
{ "type": "boolean" },
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"enabled": {
|
||||
"type": "boolean",
|
||||
"description": "Whether the job should run. Defaults to true when the key is present.",
|
||||
"default": true
|
||||
}
|
||||
},
|
||||
"additionalProperties": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"smokeJobConfig": {
|
||||
"description": "How the smoke-test job should be configured. Boolean form is shorthand for { enabled: <bool> }; object form adds a `probes` list on top of `enabled`.",
|
||||
"oneOf": [
|
||||
{ "type": "boolean" },
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"enabled": {
|
||||
"type": "boolean",
|
||||
"description": "Whether the job should run. Defaults to true when the key is present.",
|
||||
"default": true
|
||||
},
|
||||
"probes": {
|
||||
"type": "array",
|
||||
"description": "Which smoke-test probes to run. Defaults to [\"page\"]. Add \"graphql\" to also probe the GraphQL endpoint — only for editions that ship GraphQL modules (the mage-os minimal edition does not, so /graphql 404s there).",
|
||||
"items": { "enum": ["page", "graphql"] },
|
||||
"default": ["page"]
|
||||
}
|
||||
},
|
||||
"additionalProperties": true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
Vendored
+69
File diff suppressed because one or more lines are too long
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"$schema": "../check-extension.schema.json",
|
||||
"jobs": {
|
||||
"unit-test-extension": true,
|
||||
"compile-extension": true,
|
||||
"coding-standard": true,
|
||||
"integration_test": {
|
||||
"services": ["search", "cache"]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"$schema": "../check-extension.schema.json",
|
||||
"jobs": {
|
||||
"integration_test": false
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"$schema": "../check-store.schema.json",
|
||||
"jobs": {
|
||||
"unit-test": true,
|
||||
"coding-standard": true,
|
||||
"integration-test": {
|
||||
"services": ["search", "queue", "cache"]
|
||||
},
|
||||
"smoke-test": {
|
||||
"services": ["search", "queue", "cache", "nginx", "php-fpm"],
|
||||
"probes": ["page", "graphql"]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"$schema": "../check-store.schema.json",
|
||||
"jobs": {
|
||||
"smoke-test": false
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
module.exports = {
|
||||
clearMocks: true,
|
||||
moduleFileExtensions: ['js', 'ts'],
|
||||
testMatch: ['**/*.spec.ts'],
|
||||
transform: {
|
||||
'^.+\\.ts$': 'ts-jest'
|
||||
},
|
||||
verbose: true
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"name": "@graycoreio/github-actions-magento2-resolve-check-config",
|
||||
"version": "1.0.0",
|
||||
"description": "A Github Action that reads .github/<workflow>.json, validates it against the known job list, and emits resolved per-job configuration.",
|
||||
"main": "index.js",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "npx esbuild --outfile=dist/index.js --platform=node --bundle --minify src/index.ts",
|
||||
"test": "jest"
|
||||
},
|
||||
"author": "",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@actions/core": "0.0.0-PLACEHOLDER"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jest": "0.0.0-PLACEHOLDER",
|
||||
"jest": "0.0.0-PLACEHOLDER",
|
||||
"ts-jest": "0.0.0-PLACEHOLDER"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import * as core from '@actions/core';
|
||||
import * as fs from 'fs';
|
||||
import * as nodePath from 'path';
|
||||
import { assertKind } from './kind';
|
||||
import { parseMatrixInput, parseRawConfig } from './parse';
|
||||
import { resolveConfig } from './resolve';
|
||||
|
||||
export const run = async (): Promise<void> => {
|
||||
try {
|
||||
const kind = assertKind(core.getInput('kind', { required: true }));
|
||||
const matrix = parseMatrixInput(core.getInput('matrix', { required: true }));
|
||||
const configPath = core.getInput('config_path') || `.github/check-${kind}.json`;
|
||||
const workspace = process.env.GITHUB_WORKSPACE || process.cwd();
|
||||
const absolute = nodePath.resolve(workspace, configPath);
|
||||
|
||||
let raw = {};
|
||||
if (fs.existsSync(absolute)) {
|
||||
const text = fs.readFileSync(absolute, 'utf-8');
|
||||
raw = parseRawConfig(text);
|
||||
core.info(`resolve-check-config: read ${absolute}`);
|
||||
} else {
|
||||
core.info(`resolve-check-config: ${absolute} not found — emitting defaults for every known job`);
|
||||
}
|
||||
|
||||
const resolved = resolveConfig(raw, kind, matrix);
|
||||
|
||||
core.setOutput('resolved', JSON.stringify(resolved));
|
||||
} catch (error) {
|
||||
core.setFailed((error as Error).message);
|
||||
}
|
||||
}
|
||||
|
||||
run();
|
||||
@@ -0,0 +1,26 @@
|
||||
import { assertKind, isKind } from './kind';
|
||||
|
||||
describe('isKind / assertKind', () => {
|
||||
it('accepts "store"', () => {
|
||||
expect(isKind('store')).toBe(true);
|
||||
expect(assertKind('store')).toBe('store');
|
||||
});
|
||||
|
||||
it('accepts "extension"', () => {
|
||||
expect(isKind('extension')).toBe(true);
|
||||
expect(assertKind('extension')).toBe('extension');
|
||||
});
|
||||
|
||||
it('rejects other strings', () => {
|
||||
expect(isKind('taco')).toBe(false);
|
||||
expect(() => assertKind('taco')).toThrowError(/`kind` must be 'store' or 'extension'/);
|
||||
});
|
||||
|
||||
it('rejects empty input', () => {
|
||||
expect(() => assertKind('')).toThrowError(/`kind` must be 'store' or 'extension'/);
|
||||
});
|
||||
|
||||
it('rejects non-string input', () => {
|
||||
expect(() => assertKind(undefined)).toThrowError(/`kind` must be 'store' or 'extension'/);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,22 @@
|
||||
import { Kind } from './types';
|
||||
|
||||
/**
|
||||
* Type guard for the `kind` input. Use this when you have an
|
||||
* `unknown` value (e.g. from `core.getInput`) and want to narrow it
|
||||
* without throwing.
|
||||
*/
|
||||
export const isKind = (value: unknown): value is Kind =>
|
||||
value === 'store' || value === 'extension';
|
||||
|
||||
/**
|
||||
* Narrows an `unknown` (typically the raw action input) to `Kind` or
|
||||
* throws a user-facing error naming the accepted values. Prefer this
|
||||
* at the action boundary so a bad `kind` fails fast with a clear
|
||||
* message rather than later as an obscure dispatch miss.
|
||||
*/
|
||||
export const assertKind = (value: unknown): Kind => {
|
||||
if (!isKind(value)) {
|
||||
throw new Error(`check-config: \`kind\` must be 'store' or 'extension' (got ${JSON.stringify(value)})`);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
import { EXTENSION_JOBS, KNOWN_JOBS_EXTENSION, resolveExtensionConfig } from './extension';
|
||||
import { Matrix } from '../types';
|
||||
|
||||
const FULL_SERVICES = {
|
||||
mysql: { image: 'mysql:8' },
|
||||
opensearch: { image: 'opensearchproject/opensearch:2' },
|
||||
rabbitmq: { image: 'rabbitmq:3' },
|
||||
valkey: { image: 'valkey:8' },
|
||||
nginx: { image: 'nginx:1.27' },
|
||||
'php-fpm': { image: 'php:8.3-fpm' },
|
||||
};
|
||||
|
||||
const MATRIX: Matrix = {
|
||||
include: [{ php: '8.3', services: { ...FULL_SERVICES } }],
|
||||
};
|
||||
|
||||
describe('EXTENSION_JOBS', () => {
|
||||
it('declares the check-extension jobs', () => {
|
||||
expect(Object.keys(EXTENSION_JOBS).sort()).toEqual([
|
||||
'coding-standard',
|
||||
'compile-extension',
|
||||
'integration_test',
|
||||
'unit-test-extension',
|
||||
]);
|
||||
});
|
||||
|
||||
it('keeps KNOWN_JOBS_EXTENSION in sync with the map keys', () => {
|
||||
expect([...KNOWN_JOBS_EXTENSION].sort()).toEqual(Object.keys(EXTENSION_JOBS).sort());
|
||||
});
|
||||
|
||||
it('declares integration_test required tiers (no web)', () => {
|
||||
expect(EXTENSION_JOBS['integration_test'].services).toEqual([]);
|
||||
expect([...EXTENSION_JOBS['integration_test'].requiredServices!].sort()).toEqual([
|
||||
'cache',
|
||||
'db',
|
||||
'queue',
|
||||
'search',
|
||||
]);
|
||||
});
|
||||
|
||||
it('leaves the non-service jobs with empty defaults', () => {
|
||||
for (const name of ['unit-test-extension', 'compile-extension', 'coding-standard']) {
|
||||
expect(EXTENSION_JOBS[name].services).toEqual([]);
|
||||
expect(EXTENSION_JOBS[name].requiredServices).toBeUndefined();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('resolveExtensionConfig', () => {
|
||||
it('emits every known job', () => {
|
||||
const resolved = resolveExtensionConfig({}, MATRIX);
|
||||
expect(Object.keys(resolved).sort()).toEqual([
|
||||
'coding-standard',
|
||||
'compile-extension',
|
||||
'integration_test',
|
||||
'unit-test-extension',
|
||||
]);
|
||||
});
|
||||
|
||||
it('emits services={} for the non-service jobs', () => {
|
||||
const resolved = resolveExtensionConfig({}, MATRIX);
|
||||
for (const name of ['unit-test-extension', 'compile-extension', 'coding-standard']) {
|
||||
expect(resolved[name].matrix.include[0].services).toEqual({});
|
||||
}
|
||||
});
|
||||
|
||||
it('integration_test includes mysql/search/queue/cache but NOT nginx/php-fpm', () => {
|
||||
const resolved = resolveExtensionConfig({}, MATRIX);
|
||||
expect(Object.keys(resolved['integration_test'].matrix.include[0].services!).sort()).toEqual([
|
||||
'mysql',
|
||||
'opensearch',
|
||||
'rabbitmq',
|
||||
'valkey',
|
||||
]);
|
||||
});
|
||||
|
||||
it('keeps integration_test required tiers even when caller overrides services to []', () => {
|
||||
const resolved = resolveExtensionConfig(
|
||||
{ jobs: { integration_test: { services: [] } } },
|
||||
MATRIX,
|
||||
);
|
||||
expect(Object.keys(resolved['integration_test'].matrix.include[0].services!).sort()).toEqual([
|
||||
'mysql',
|
||||
'opensearch',
|
||||
'rabbitmq',
|
||||
'valkey',
|
||||
]);
|
||||
});
|
||||
|
||||
it('throws on a typo in the job name', () => {
|
||||
expect(() => resolveExtensionConfig({ jobs: { 'inteegration_test': false } }, MATRIX)).toThrowError(
|
||||
/unknown job "inteegration_test" for kind "extension"/
|
||||
);
|
||||
});
|
||||
|
||||
it('throws when a store-only job name is used', () => {
|
||||
expect(() => resolveExtensionConfig({ jobs: { 'smoke-test': false } }, MATRIX)).toThrowError(
|
||||
/unknown job "smoke-test" for kind "extension"/
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,29 @@
|
||||
import { resolveJobs } from '../parse';
|
||||
import { JobDefaults, Matrix, RawConfig, ResolvedConfig } from '../types';
|
||||
|
||||
/**
|
||||
* Per-job defaults for the `check-extension.yaml` reusable workflow.
|
||||
* Edit this map when a job is added, removed, or renamed in that
|
||||
* workflow — keys are validated against caller config and the values
|
||||
* supply the default tier list used when the caller doesn't override
|
||||
* `services` themselves.
|
||||
*/
|
||||
export const EXTENSION_JOBS: Record<string, JobDefaults> = {
|
||||
'unit-test-extension': { services: [] },
|
||||
'compile-extension': { services: [] },
|
||||
'coding-standard': { services: [] },
|
||||
'integration_test': {
|
||||
services: [],
|
||||
requiredServices: ['db', 'search', 'queue', 'cache'],
|
||||
},
|
||||
};
|
||||
|
||||
export const KNOWN_JOBS_EXTENSION: readonly string[] = Object.keys(EXTENSION_JOBS);
|
||||
|
||||
/**
|
||||
* Resolves a parsed config file + supported-version matrix against
|
||||
* the check-extension job list. Thin wrapper that binds the kind and
|
||||
* the per-job defaults so callers don't repeat the wiring.
|
||||
*/
|
||||
export const resolveExtensionConfig = (raw: RawConfig, matrix: Matrix): ResolvedConfig =>
|
||||
resolveJobs(raw, 'extension', EXTENSION_JOBS, matrix);
|
||||
@@ -0,0 +1,122 @@
|
||||
import { KNOWN_JOBS_STORE, resolveStoreConfig, STORE_JOBS } from './store';
|
||||
import { Matrix } from '../types';
|
||||
|
||||
const MATRIX: Matrix = {
|
||||
include: [{
|
||||
php: '8.3',
|
||||
services: {
|
||||
mysql: { image: 'mysql:8' },
|
||||
opensearch: { image: 'opensearchproject/opensearch:2' },
|
||||
rabbitmq: { image: 'rabbitmq:3' },
|
||||
valkey: { image: 'valkey:8' },
|
||||
nginx: { image: 'nginx:1.27' },
|
||||
'php-fpm': { image: 'php:8.3-fpm' },
|
||||
},
|
||||
}],
|
||||
};
|
||||
|
||||
describe('STORE_JOBS', () => {
|
||||
it('declares the check-store jobs', () => {
|
||||
expect(Object.keys(STORE_JOBS).sort()).toEqual(['coding-standard', 'smoke-test', 'unit-test']);
|
||||
});
|
||||
|
||||
it('declares smoke-test required tiers (end-user cannot toggle)', () => {
|
||||
expect(STORE_JOBS['smoke-test'].services).toEqual([]);
|
||||
expect([...STORE_JOBS['smoke-test'].requiredServices!].sort()).toEqual([
|
||||
'cache',
|
||||
'db',
|
||||
'queue',
|
||||
'search',
|
||||
'web',
|
||||
]);
|
||||
});
|
||||
|
||||
it('defaults smoke-test to the page probe only (graphql is opt-in)', () => {
|
||||
expect(STORE_JOBS['smoke-test'].probes).toEqual(['page']);
|
||||
});
|
||||
|
||||
it('exposes empty service defaults for unit-test and coding-standard', () => {
|
||||
expect(STORE_JOBS['unit-test'].services).toEqual([]);
|
||||
expect(STORE_JOBS['coding-standard'].services).toEqual([]);
|
||||
});
|
||||
|
||||
it('keeps KNOWN_JOBS_STORE in sync with the map keys', () => {
|
||||
expect([...KNOWN_JOBS_STORE].sort()).toEqual(Object.keys(STORE_JOBS).sort());
|
||||
});
|
||||
});
|
||||
|
||||
describe('resolveStoreConfig', () => {
|
||||
it('emits every known job with default tier expansion, always including mysql for smoke-test', () => {
|
||||
const resolved = resolveStoreConfig({}, MATRIX);
|
||||
expect(Object.keys(resolved).sort()).toEqual(['coding-standard', 'smoke-test', 'unit-test']);
|
||||
expect(resolved['unit-test'].matrix.include[0].services).toEqual({});
|
||||
expect(Object.keys(resolved['smoke-test'].matrix.include[0].services!).sort()).toEqual([
|
||||
'mysql',
|
||||
'nginx',
|
||||
'opensearch',
|
||||
'php-fpm',
|
||||
'rabbitmq',
|
||||
'valkey',
|
||||
]);
|
||||
});
|
||||
|
||||
it('keeps every required service even when caller overrides smoke-test services to []', () => {
|
||||
const resolved = resolveStoreConfig(
|
||||
{ jobs: { 'smoke-test': { services: [] } } },
|
||||
MATRIX,
|
||||
);
|
||||
expect(Object.keys(resolved['smoke-test'].matrix.include[0].services!).sort()).toEqual([
|
||||
'mysql',
|
||||
'nginx',
|
||||
'opensearch',
|
||||
'php-fpm',
|
||||
'rabbitmq',
|
||||
'valkey',
|
||||
]);
|
||||
});
|
||||
|
||||
it('honors enabled=false for a job', () => {
|
||||
const resolved = resolveStoreConfig(
|
||||
{ jobs: { 'smoke-test': false } },
|
||||
MATRIX,
|
||||
);
|
||||
expect(resolved['smoke-test'].enabled).toBe(false);
|
||||
});
|
||||
|
||||
it('emits the default page-only probe list for smoke-test', () => {
|
||||
const resolved = resolveStoreConfig({}, MATRIX);
|
||||
expect(resolved['smoke-test'].probes).toEqual(['page']);
|
||||
});
|
||||
|
||||
it('honors a smoke-test probes override', () => {
|
||||
const resolved = resolveStoreConfig(
|
||||
{ jobs: { 'smoke-test': { probes: ['page', 'graphql'] } } },
|
||||
MATRIX,
|
||||
);
|
||||
expect(resolved['smoke-test'].probes).toEqual(['page', 'graphql']);
|
||||
});
|
||||
|
||||
it('does not emit probes on jobs without a probe concept', () => {
|
||||
const resolved = resolveStoreConfig({}, MATRIX);
|
||||
expect(resolved['unit-test'].probes).toBeUndefined();
|
||||
expect(resolved['coding-standard'].probes).toBeUndefined();
|
||||
});
|
||||
|
||||
it('rejects probes on a job that does not support it', () => {
|
||||
expect(() => resolveStoreConfig({ jobs: { 'unit-test': { probes: ['page'] } } }, MATRIX)).toThrowError(
|
||||
/job "unit-test" does not support "probes"/
|
||||
);
|
||||
});
|
||||
|
||||
it('throws on a typo in the job name', () => {
|
||||
expect(() => resolveStoreConfig({ jobs: { 'smkoe-test': false } }, MATRIX)).toThrowError(
|
||||
/unknown job "smkoe-test" for kind "store"/
|
||||
);
|
||||
});
|
||||
|
||||
it('throws when an extension-only job name is used', () => {
|
||||
expect(() => resolveStoreConfig({ jobs: { 'unit-test-extension': false } }, MATRIX)).toThrowError(
|
||||
/unknown job "unit-test-extension" for kind "store"/
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,29 @@
|
||||
import { resolveJobs } from '../parse';
|
||||
import { JobDefaults, Matrix, RawConfig, ResolvedConfig } from '../types';
|
||||
|
||||
/**
|
||||
* Per-job defaults for the `check-store.yaml` reusable workflow.
|
||||
* Edit this map when a job is added, removed, or renamed in that
|
||||
* workflow — keys are validated against caller config and the values
|
||||
* supply the default tier list used when the caller doesn't override
|
||||
* `services` themselves.
|
||||
*/
|
||||
export const STORE_JOBS: Record<string, JobDefaults> = {
|
||||
'unit-test': { services: [] },
|
||||
'coding-standard': { services: [] },
|
||||
'smoke-test': {
|
||||
services: [],
|
||||
requiredServices: ['db', 'search', 'queue', 'cache', 'web'],
|
||||
probes: ['page'],
|
||||
},
|
||||
};
|
||||
|
||||
export const KNOWN_JOBS_STORE: readonly string[] = Object.keys(STORE_JOBS);
|
||||
|
||||
/**
|
||||
* Resolves a parsed config file + supported-version matrix against
|
||||
* the check-store job list. Thin wrapper that binds the kind and the
|
||||
* per-job defaults so callers don't repeat the wiring.
|
||||
*/
|
||||
export const resolveStoreConfig = (raw: RawConfig, matrix: Matrix): ResolvedConfig =>
|
||||
resolveJobs(raw, 'store', STORE_JOBS, matrix);
|
||||
@@ -0,0 +1,367 @@
|
||||
import {
|
||||
filterEntryServices,
|
||||
filterMatrixForJob,
|
||||
mergeRequiredTiers,
|
||||
normalizeJobEntry,
|
||||
normalizeProbes,
|
||||
parseMatrixInput,
|
||||
parseRawConfig,
|
||||
resolveJobs,
|
||||
} from './parse';
|
||||
import { JobDefaults, Matrix } from './types';
|
||||
|
||||
const FULL_SERVICES = {
|
||||
mysql: { image: 'mysql:8' },
|
||||
opensearch: { image: 'opensearchproject/opensearch:2' },
|
||||
rabbitmq: { image: 'rabbitmq:3' },
|
||||
valkey: { image: 'valkey:8' },
|
||||
nginx: { image: 'nginx:1.27' },
|
||||
'php-fpm': { image: 'php:8.3-fpm' },
|
||||
};
|
||||
|
||||
const MATRIX: Matrix = {
|
||||
include: [{ php: '8.3', services: { ...FULL_SERVICES } }],
|
||||
};
|
||||
|
||||
const noDefaults: JobDefaults = { services: [] };
|
||||
const smokeDefaults: JobDefaults = { services: ['search', 'queue', 'cache', 'web'] };
|
||||
const probeDefaults: JobDefaults = { services: [], probes: ['page'] };
|
||||
|
||||
describe('normalizeJobEntry', () => {
|
||||
it('defaults enabled=true and uses the default tiers when entry is undefined', () => {
|
||||
expect(normalizeJobEntry('smoke-test', undefined, smokeDefaults)).toEqual({
|
||||
enabled: true,
|
||||
tiers: smokeDefaults.services,
|
||||
});
|
||||
});
|
||||
|
||||
it('treats true shorthand as enabled with defaults', () => {
|
||||
expect(normalizeJobEntry('smoke-test', true, smokeDefaults)).toEqual({
|
||||
enabled: true,
|
||||
tiers: smokeDefaults.services,
|
||||
});
|
||||
});
|
||||
|
||||
it('treats false shorthand as disabled with defaults', () => {
|
||||
expect(normalizeJobEntry('smoke-test', false, smokeDefaults)).toEqual({
|
||||
enabled: false,
|
||||
tiers: smokeDefaults.services,
|
||||
});
|
||||
});
|
||||
|
||||
it('empty object is enabled with defaults', () => {
|
||||
expect(normalizeJobEntry('smoke-test', {}, smokeDefaults)).toEqual({
|
||||
enabled: true,
|
||||
tiers: smokeDefaults.services,
|
||||
});
|
||||
});
|
||||
|
||||
it('preserves enabled when explicitly set', () => {
|
||||
expect(normalizeJobEntry('smoke-test', { enabled: false }, smokeDefaults)).toEqual({
|
||||
enabled: false,
|
||||
tiers: smokeDefaults.services,
|
||||
});
|
||||
});
|
||||
|
||||
it('overrides the default tiers when services is set', () => {
|
||||
expect(normalizeJobEntry('smoke-test', { services: ['cache', 'web'] }, smokeDefaults)).toEqual({
|
||||
enabled: true,
|
||||
tiers: ['cache', 'web'],
|
||||
});
|
||||
});
|
||||
|
||||
it('accepts an empty services array as "no services"', () => {
|
||||
expect(normalizeJobEntry('smoke-test', { services: [] }, smokeDefaults)).toEqual({
|
||||
enabled: true,
|
||||
tiers: [],
|
||||
});
|
||||
});
|
||||
|
||||
it('throws when entry is a non-array primitive other than boolean', () => {
|
||||
expect(() => normalizeJobEntry('unit-test', 'true' as never, noDefaults)).toThrowError(
|
||||
/must be a boolean or an object/
|
||||
);
|
||||
});
|
||||
|
||||
it('throws when entry is an array', () => {
|
||||
expect(() => normalizeJobEntry('unit-test', [] as never, noDefaults)).toThrowError(/got array/);
|
||||
});
|
||||
|
||||
it('throws when services is not an array', () => {
|
||||
expect(() => normalizeJobEntry('smoke-test', { services: 'search' } as never, smokeDefaults)).toThrowError(
|
||||
/services must be an array of tier names/
|
||||
);
|
||||
});
|
||||
|
||||
it('throws when services contains an unknown tier', () => {
|
||||
expect(() => normalizeJobEntry('smoke-test', { services: ['llm'] } as never, smokeDefaults)).toThrowError(
|
||||
/services contains unknown tier "llm"/
|
||||
);
|
||||
});
|
||||
|
||||
it('carries the default probes when the entry omits them', () => {
|
||||
expect(normalizeJobEntry('smoke-test', { services: [] }, probeDefaults)).toEqual({
|
||||
enabled: true,
|
||||
tiers: [],
|
||||
probes: ['page'],
|
||||
});
|
||||
});
|
||||
|
||||
it('carries the default probes for the boolean shorthand', () => {
|
||||
expect(normalizeJobEntry('smoke-test', true, probeDefaults)).toEqual({
|
||||
enabled: true,
|
||||
tiers: [],
|
||||
probes: ['page'],
|
||||
});
|
||||
});
|
||||
|
||||
it('overrides the default probes when probes is set', () => {
|
||||
expect(normalizeJobEntry('smoke-test', { probes: ['page', 'graphql'] }, probeDefaults)).toEqual({
|
||||
enabled: true,
|
||||
tiers: [],
|
||||
probes: ['page', 'graphql'],
|
||||
});
|
||||
});
|
||||
|
||||
it('omits probes for a job that declares no probe defaults', () => {
|
||||
expect(normalizeJobEntry('unit-test', { services: [] }, noDefaults).probes).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('normalizeProbes', () => {
|
||||
it('returns the defaults when probes is omitted', () => {
|
||||
expect(normalizeProbes('smoke-test', undefined, ['page'])).toEqual(['page']);
|
||||
});
|
||||
|
||||
it('returns undefined for a job with no probe defaults when omitted', () => {
|
||||
expect(normalizeProbes('unit-test', undefined, undefined)).toBeUndefined();
|
||||
});
|
||||
|
||||
it('throws when probes is set on a job that does not support it', () => {
|
||||
expect(() => normalizeProbes('unit-test', ['page'], undefined)).toThrowError(
|
||||
/job "unit-test" does not support "probes"/
|
||||
);
|
||||
});
|
||||
|
||||
it('throws when probes is not an array', () => {
|
||||
expect(() => normalizeProbes('smoke-test', 'page', ['page'])).toThrowError(
|
||||
/probes must be an array of probe names/
|
||||
);
|
||||
});
|
||||
|
||||
it('throws when probes contains an unknown probe', () => {
|
||||
expect(() => normalizeProbes('smoke-test', ['rest'], ['page'])).toThrowError(
|
||||
/probes contains unknown probe "rest"/
|
||||
);
|
||||
});
|
||||
|
||||
it('accepts an empty probes array', () => {
|
||||
expect(normalizeProbes('smoke-test', [], ['page'])).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('mergeRequiredTiers', () => {
|
||||
it('returns the input list when required is undefined', () => {
|
||||
expect(mergeRequiredTiers(['cache'], undefined)).toEqual(['cache']);
|
||||
});
|
||||
|
||||
it('returns the input list when required is empty', () => {
|
||||
expect(mergeRequiredTiers(['cache'], [])).toEqual(['cache']);
|
||||
});
|
||||
|
||||
it('prepends required tiers ahead of the input tiers', () => {
|
||||
expect(mergeRequiredTiers(['cache', 'web'], ['db'])).toEqual(['db', 'cache', 'web']);
|
||||
});
|
||||
|
||||
it('deduplicates when a required tier already appears in the input', () => {
|
||||
expect(mergeRequiredTiers(['db', 'cache'], ['db'])).toEqual(['db', 'cache']);
|
||||
});
|
||||
|
||||
it('deduplicates within required itself', () => {
|
||||
expect(mergeRequiredTiers(['cache'], ['db', 'db'])).toEqual(['db', 'cache']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('filterEntryServices', () => {
|
||||
it('returns services={} for an empty tier list', () => {
|
||||
const out = filterEntryServices({ php: '8.3', services: FULL_SERVICES }, []);
|
||||
expect(out.services).toEqual({});
|
||||
expect(out.php).toBe('8.3');
|
||||
});
|
||||
|
||||
it('keeps only services in the requested tiers', () => {
|
||||
const out = filterEntryServices({ php: '8.3', services: FULL_SERVICES }, ['cache', 'web']);
|
||||
expect(Object.keys(out.services!).sort()).toEqual(['nginx', 'php-fpm', 'valkey']);
|
||||
});
|
||||
|
||||
it('drops services that the matrix doesn\'t carry (elasticsearch absent)', () => {
|
||||
const out = filterEntryServices({ services: { opensearch: FULL_SERVICES.opensearch } }, ['search']);
|
||||
expect(Object.keys(out.services!)).toEqual(['opensearch']);
|
||||
});
|
||||
|
||||
it('emits services={} when the entry has no services map', () => {
|
||||
const out = filterEntryServices({ php: '8.3' }, ['cache']);
|
||||
expect(out.services).toEqual({});
|
||||
});
|
||||
});
|
||||
|
||||
describe('filterMatrixForJob', () => {
|
||||
it('preserves matrix shape, mapping every entry through filterEntryServices', () => {
|
||||
const out = filterMatrixForJob(MATRIX, ['queue']);
|
||||
expect(out.include).toHaveLength(1);
|
||||
expect(Object.keys(out.include[0].services!)).toEqual(['rabbitmq']);
|
||||
});
|
||||
|
||||
it('passes through unrelated top-level matrix keys', () => {
|
||||
const out = filterMatrixForJob({ ...MATRIX, magento: ['2.4.7'] } as Matrix, []);
|
||||
expect((out as Matrix).magento).toEqual(['2.4.7']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('resolveJobs', () => {
|
||||
const jobs: Record<string, JobDefaults> = {
|
||||
'unit-test': noDefaults,
|
||||
'smoke-test': smokeDefaults,
|
||||
};
|
||||
|
||||
it('emits every known job, defaulted-enabled, when raw is empty', () => {
|
||||
const out = resolveJobs({}, 'store', jobs, MATRIX);
|
||||
expect(Object.keys(out).sort()).toEqual(['smoke-test', 'unit-test']);
|
||||
expect(out['unit-test'].enabled).toBe(true);
|
||||
expect(out['smoke-test'].enabled).toBe(true);
|
||||
});
|
||||
|
||||
it('emits services={} on entries for a no-default job', () => {
|
||||
const out = resolveJobs({}, 'store', jobs, MATRIX);
|
||||
expect(out['unit-test'].matrix.include[0].services).toEqual({});
|
||||
});
|
||||
|
||||
it('expands the smoke-test default tiers across the matrix entry', () => {
|
||||
const out = resolveJobs({}, 'store', jobs, MATRIX);
|
||||
expect(Object.keys(out['smoke-test'].matrix.include[0].services!).sort()).toEqual([
|
||||
'nginx',
|
||||
'opensearch',
|
||||
'php-fpm',
|
||||
'rabbitmq',
|
||||
'valkey',
|
||||
]);
|
||||
});
|
||||
|
||||
it('applies a caller-supplied services override', () => {
|
||||
const out = resolveJobs(
|
||||
{ jobs: { 'smoke-test': { services: ['cache'] } } },
|
||||
'store',
|
||||
jobs,
|
||||
MATRIX,
|
||||
);
|
||||
expect(Object.keys(out['smoke-test'].matrix.include[0].services!)).toEqual(['valkey']);
|
||||
});
|
||||
|
||||
it('always merges requiredServices into the matrix even when caller overrides services', () => {
|
||||
const withRequired: Record<string, JobDefaults> = {
|
||||
'smoke-test': { ...smokeDefaults, requiredServices: ['db'] },
|
||||
};
|
||||
const out = resolveJobs(
|
||||
{ jobs: { 'smoke-test': { services: ['cache'] } } },
|
||||
'store',
|
||||
withRequired,
|
||||
MATRIX,
|
||||
);
|
||||
expect(Object.keys(out['smoke-test'].matrix.include[0].services!).sort()).toEqual(['mysql', 'valkey']);
|
||||
});
|
||||
|
||||
it('keeps requiredServices even when caller overrides services to []', () => {
|
||||
const withRequired: Record<string, JobDefaults> = {
|
||||
'smoke-test': { ...smokeDefaults, requiredServices: ['db'] },
|
||||
};
|
||||
const out = resolveJobs(
|
||||
{ jobs: { 'smoke-test': { services: [] } } },
|
||||
'store',
|
||||
withRequired,
|
||||
MATRIX,
|
||||
);
|
||||
expect(Object.keys(out['smoke-test'].matrix.include[0].services!)).toEqual(['mysql']);
|
||||
});
|
||||
|
||||
it('honors caller enabled=false but still emits a filtered matrix', () => {
|
||||
const out = resolveJobs(
|
||||
{ jobs: { 'smoke-test': false } },
|
||||
'store',
|
||||
jobs,
|
||||
MATRIX,
|
||||
);
|
||||
expect(out['smoke-test'].enabled).toBe(false);
|
||||
expect(out['smoke-test'].matrix.include[0].services).toBeDefined();
|
||||
});
|
||||
|
||||
it('throws on unknown job names with the kind in the message', () => {
|
||||
expect(() => resolveJobs({ jobs: { taco: false } }, 'store', jobs, MATRIX)).toThrowError(
|
||||
/unknown job "taco" for kind "store"/
|
||||
);
|
||||
});
|
||||
|
||||
it('throws when `jobs` is not an object', () => {
|
||||
expect(() => resolveJobs({ jobs: 'oops' } as never, 'store', jobs, MATRIX)).toThrowError(
|
||||
/`jobs` must be an object/
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('parseRawConfig', () => {
|
||||
it('returns an empty object for empty input', () => {
|
||||
expect(parseRawConfig('')).toEqual({});
|
||||
});
|
||||
|
||||
it('returns an empty object for whitespace input', () => {
|
||||
expect(parseRawConfig(' \n ')).toEqual({});
|
||||
});
|
||||
|
||||
it('parses a valid object', () => {
|
||||
expect(parseRawConfig('{"jobs": {"unit-test": true}}')).toEqual({
|
||||
jobs: { 'unit-test': true },
|
||||
});
|
||||
});
|
||||
|
||||
it('throws on syntactically invalid JSON', () => {
|
||||
expect(() => parseRawConfig('{not json}')).toThrowError(/failed to parse JSON/);
|
||||
});
|
||||
|
||||
it('throws when top level is an array', () => {
|
||||
expect(() => parseRawConfig('[]')).toThrowError(/top-level value must be an object/);
|
||||
});
|
||||
|
||||
it('throws when top level is a primitive', () => {
|
||||
expect(() => parseRawConfig('"hello"')).toThrowError(/top-level value must be an object/);
|
||||
});
|
||||
|
||||
it('throws when top level is null', () => {
|
||||
expect(() => parseRawConfig('null')).toThrowError(/top-level value must be an object/);
|
||||
});
|
||||
});
|
||||
|
||||
describe('parseMatrixInput', () => {
|
||||
it('parses a valid matrix', () => {
|
||||
const out = parseMatrixInput('{"include": [{"php": "8.3"}]}');
|
||||
expect(out.include).toEqual([{ php: '8.3' }]);
|
||||
});
|
||||
|
||||
it('throws on empty input', () => {
|
||||
expect(() => parseMatrixInput('')).toThrowError(/`matrix` input is required/);
|
||||
});
|
||||
|
||||
it('throws on invalid JSON', () => {
|
||||
expect(() => parseMatrixInput('{nope}')).toThrowError(/failed to parse `matrix` input/);
|
||||
});
|
||||
|
||||
it('throws when top level is an array', () => {
|
||||
expect(() => parseMatrixInput('[]')).toThrowError(/`matrix` must be a JSON object/);
|
||||
});
|
||||
|
||||
it('throws when include is missing', () => {
|
||||
expect(() => parseMatrixInput('{}')).toThrowError(/`matrix.include` must be an array/);
|
||||
});
|
||||
|
||||
it('throws when include is not an array', () => {
|
||||
expect(() => parseMatrixInput('{"include": "nope"}')).toThrowError(/`matrix.include` must be an array/);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,211 @@
|
||||
import { JobDefaults, Kind, Matrix, MatrixEntry, RawConfig, RawJobConfig, ResolvedConfig, ResolvedJobConfig, Services } from './types';
|
||||
import { isTier, servicesForTiers, Tier } from './tier-map';
|
||||
import { isProbe, Probe } from './probe';
|
||||
|
||||
/**
|
||||
* Normalizes the `probes` value from a job entry. Returns the
|
||||
* caller's list when present (validated), the job's default probe
|
||||
* list when omitted, or `undefined` for jobs that have no probe
|
||||
* concept. Throws if a job without probe defaults is given `probes`.
|
||||
*/
|
||||
export const normalizeProbes = (
|
||||
jobName: string,
|
||||
raw: unknown,
|
||||
defaults: readonly Probe[] | undefined,
|
||||
): readonly Probe[] | undefined => {
|
||||
if (raw === undefined) {
|
||||
return defaults;
|
||||
}
|
||||
if (defaults === undefined) {
|
||||
throw new Error(`check-config: job "${jobName}" does not support "probes"`);
|
||||
}
|
||||
if (!Array.isArray(raw)) {
|
||||
throw new Error(`check-config: job "${jobName}".probes must be an array of probe names`);
|
||||
}
|
||||
const probes: Probe[] = [];
|
||||
for (const value of raw) {
|
||||
if (!isProbe(value)) {
|
||||
throw new Error(`check-config: job "${jobName}".probes contains unknown probe "${String(value)}"`);
|
||||
}
|
||||
probes.push(value);
|
||||
}
|
||||
return probes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes a single raw job entry to (enabled, tiers, probes).
|
||||
* Accepts the boolean shorthand and the object form. Validates the
|
||||
* shape, the `services` tier list, and the `probes` list; throws on
|
||||
* unexpected input. The caller supplies the per-job defaults, used
|
||||
* when `services`/`probes` are omitted from the entry. `probes` is
|
||||
* `undefined` for jobs that declare no probe defaults.
|
||||
*/
|
||||
export const normalizeJobEntry = (
|
||||
jobName: string,
|
||||
raw: RawJobConfig | undefined,
|
||||
defaults: JobDefaults,
|
||||
): { enabled: boolean; tiers: readonly Tier[]; probes?: readonly Probe[] } => {
|
||||
if (raw === undefined) {
|
||||
return { enabled: true, tiers: defaults.services, probes: defaults.probes };
|
||||
}
|
||||
if (typeof raw === 'boolean') {
|
||||
return { enabled: raw, tiers: defaults.services, probes: defaults.probes };
|
||||
}
|
||||
if (raw === null || typeof raw !== 'object' || Array.isArray(raw)) {
|
||||
throw new Error(
|
||||
`check-config: job "${jobName}" must be a boolean or an object (got ${Array.isArray(raw) ? 'array' : typeof raw})`
|
||||
);
|
||||
}
|
||||
const { enabled, services, probes } = raw as { enabled?: unknown; services?: unknown; probes?: unknown };
|
||||
const enabledValue = enabled === undefined ? true : Boolean(enabled);
|
||||
const resolvedProbes = normalizeProbes(jobName, probes, defaults.probes);
|
||||
|
||||
if (services === undefined) {
|
||||
return { enabled: enabledValue, tiers: defaults.services, probes: resolvedProbes };
|
||||
}
|
||||
if (!Array.isArray(services)) {
|
||||
throw new Error(`check-config: job "${jobName}".services must be an array of tier names`);
|
||||
}
|
||||
const tiers: Tier[] = [];
|
||||
for (const value of services) {
|
||||
if (!isTier(value)) {
|
||||
throw new Error(`check-config: job "${jobName}".services contains unknown tier "${String(value)}"`);
|
||||
}
|
||||
tiers.push(value);
|
||||
}
|
||||
return { enabled: enabledValue, tiers, probes: resolvedProbes };
|
||||
}
|
||||
|
||||
/**
|
||||
* Merges a job's `requiredServices` into the resolved tier list,
|
||||
* deduplicating while preserving order (required tiers first, then
|
||||
* the caller/default tiers in their original order).
|
||||
*/
|
||||
export const mergeRequiredTiers = (
|
||||
tiers: readonly Tier[],
|
||||
required: readonly Tier[] | undefined,
|
||||
): readonly Tier[] => {
|
||||
if (!required || required.length === 0) return tiers;
|
||||
const seen = new Set<Tier>();
|
||||
const merged: Tier[] = [];
|
||||
for (const tier of required) {
|
||||
if (!seen.has(tier)) { seen.add(tier); merged.push(tier); }
|
||||
}
|
||||
for (const tier of tiers) {
|
||||
if (!seen.has(tier)) { seen.add(tier); merged.push(tier); }
|
||||
}
|
||||
return merged;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a copy of `entry` with `services` filtered to the concrete
|
||||
* names produced by expanding `tiers` through the tier-map. An empty
|
||||
* tier list yields `services: {}`.
|
||||
*/
|
||||
export const filterEntryServices = (entry: MatrixEntry, tiers: readonly Tier[]): MatrixEntry => {
|
||||
const keep = servicesForTiers(tiers);
|
||||
const original = entry.services ?? {};
|
||||
const filtered: Services = {};
|
||||
for (const [name, config] of Object.entries(original)) {
|
||||
if (keep.has(name)) filtered[name] = config;
|
||||
}
|
||||
return { ...entry, services: filtered };
|
||||
}
|
||||
|
||||
/**
|
||||
* Per-job filter applied to the supported-version matrix: returns a
|
||||
* shallow clone with every entry's `services` narrowed to the tiers
|
||||
* the job needs.
|
||||
*/
|
||||
export const filterMatrixForJob = (matrix: Matrix, tiers: readonly Tier[]): Matrix => ({
|
||||
...matrix,
|
||||
include: matrix.include.map(entry => filterEntryServices(entry, tiers)),
|
||||
});
|
||||
|
||||
/**
|
||||
* Shared per-kind resolver: walks the per-kind job map and emits one
|
||||
* `ResolvedJobConfig` per known job. Caller-supplied jobs override
|
||||
* the defaults; jobs the caller omitted still appear, carrying the
|
||||
* default `enabled: true` and the default tier list. Rejects unknown
|
||||
* job names from the config so typos surface in CI.
|
||||
*/
|
||||
export const resolveJobs = (
|
||||
raw: RawConfig,
|
||||
kind: Kind,
|
||||
jobs: Record<string, JobDefaults>,
|
||||
matrix: Matrix,
|
||||
): ResolvedConfig => {
|
||||
const rawJobs = raw.jobs ?? {};
|
||||
if (rawJobs === null || typeof rawJobs !== 'object' || Array.isArray(rawJobs)) {
|
||||
throw new Error(`check-config: \`jobs\` must be an object`);
|
||||
}
|
||||
for (const name of Object.keys(rawJobs)) {
|
||||
if (!(name in jobs)) {
|
||||
throw new Error(
|
||||
`check-config: unknown job "${name}" for kind "${kind}". Known jobs: ${Object.keys(jobs).join(', ')}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const resolved: ResolvedConfig = {};
|
||||
for (const [name, defaults] of Object.entries(jobs)) {
|
||||
const entry = (rawJobs as Record<string, RawJobConfig>)[name];
|
||||
const { enabled, tiers, probes } = normalizeJobEntry(name, entry, defaults);
|
||||
const finalTiers = mergeRequiredTiers(tiers, defaults.requiredServices);
|
||||
const resolvedEntry: ResolvedJobConfig = {
|
||||
enabled,
|
||||
matrix: filterMatrixForJob(matrix, finalTiers),
|
||||
};
|
||||
if (probes !== undefined) {
|
||||
resolvedEntry.probes = [...probes];
|
||||
}
|
||||
resolved[name] = resolvedEntry;
|
||||
}
|
||||
return resolved;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a JSON string into a RawConfig with shape validation
|
||||
* (must be an object, not an array or primitive). Empty/whitespace
|
||||
* input yields an empty config.
|
||||
*/
|
||||
export const parseRawConfig = (jsonText: string): RawConfig => {
|
||||
const trimmed = jsonText.trim();
|
||||
if (trimmed === '') return {};
|
||||
let parsed: unknown;
|
||||
try {
|
||||
parsed = JSON.parse(trimmed);
|
||||
} catch (e) {
|
||||
throw new Error(`check-config: failed to parse JSON: ${(e as Error).message}`);
|
||||
}
|
||||
if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
||||
throw new Error(`check-config: top-level value must be an object`);
|
||||
}
|
||||
return parsed as RawConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the `matrix` action input. Validates the top-level shape
|
||||
* (must be an object with an `include` array) so a malformed input
|
||||
* fails with a clear message at the boundary.
|
||||
*/
|
||||
export const parseMatrixInput = (jsonText: string): Matrix => {
|
||||
const trimmed = jsonText.trim();
|
||||
if (trimmed === '') {
|
||||
throw new Error('check-config: `matrix` input is required');
|
||||
}
|
||||
let parsed: unknown;
|
||||
try {
|
||||
parsed = JSON.parse(trimmed);
|
||||
} catch (e) {
|
||||
throw new Error(`check-config: failed to parse \`matrix\` input as JSON: ${(e as Error).message}`);
|
||||
}
|
||||
if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
||||
throw new Error('check-config: `matrix` must be a JSON object');
|
||||
}
|
||||
const include = (parsed as Record<string, unknown>).include;
|
||||
if (!Array.isArray(include)) {
|
||||
throw new Error('check-config: `matrix.include` must be an array');
|
||||
}
|
||||
return parsed as Matrix;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
/**
|
||||
* A smoke-test probe the check-store workflow can run against a
|
||||
* running store. `page` does a GET / and asserts a non-empty title;
|
||||
* `graphql` POSTs a storeConfig query to /graphql. Probes are opt-in
|
||||
* per job because not every edition exposes every surface (e.g. the
|
||||
* mage-os minimal edition ships no GraphQL modules, so /graphql 404s).
|
||||
*/
|
||||
export const PROBES = ['page', 'graphql'] as const;
|
||||
|
||||
export type Probe = (typeof PROBES)[number];
|
||||
|
||||
export const isProbe = (value: unknown): value is Probe =>
|
||||
typeof value === 'string' && (PROBES as readonly string[]).includes(value);
|
||||
@@ -0,0 +1,39 @@
|
||||
import { resolveConfig } from './resolve';
|
||||
import { Matrix } from './types';
|
||||
|
||||
const MATRIX: Matrix = {
|
||||
include: [{
|
||||
php: '8.3',
|
||||
services: {
|
||||
mysql: { image: 'mysql:8' },
|
||||
opensearch: { image: 'opensearchproject/opensearch:2' },
|
||||
rabbitmq: { image: 'rabbitmq:3' },
|
||||
valkey: { image: 'valkey:8' },
|
||||
nginx: { image: 'nginx:1.27' },
|
||||
'php-fpm': { image: 'php:8.3-fpm' },
|
||||
},
|
||||
}],
|
||||
};
|
||||
|
||||
describe('resolveConfig', () => {
|
||||
it('routes kind=store to the store resolver', () => {
|
||||
const resolved = resolveConfig({ jobs: { 'smoke-test': false } }, 'store', MATRIX);
|
||||
expect(resolved['smoke-test'].enabled).toBe(false);
|
||||
expect(resolved['unit-test'].enabled).toBe(true);
|
||||
});
|
||||
|
||||
it('routes kind=extension to the extension resolver', () => {
|
||||
const resolved = resolveConfig({ jobs: { 'compile-extension': false } }, 'extension', MATRIX);
|
||||
expect(resolved['compile-extension'].enabled).toBe(false);
|
||||
expect(resolved['integration_test'].enabled).toBe(true);
|
||||
});
|
||||
|
||||
it('rejects a job name from the other kind', () => {
|
||||
expect(() => resolveConfig({ jobs: { 'smoke-test': false } }, 'extension', MATRIX)).toThrowError(
|
||||
/unknown job "smoke-test" for kind "extension"/
|
||||
);
|
||||
expect(() => resolveConfig({ jobs: { 'unit-test-extension': false } }, 'store', MATRIX)).toThrowError(
|
||||
/unknown job "unit-test-extension" for kind "store"/
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,13 @@
|
||||
import { Kind, Matrix, RawConfig, ResolvedConfig } from './types';
|
||||
import { resolveStoreConfig } from './kinds/store';
|
||||
import { resolveExtensionConfig } from './kinds/extension';
|
||||
|
||||
/**
|
||||
* Dispatches to the per-kind resolver. Each kind owns its own list
|
||||
* of jobs and per-job defaults; this function just routes the call
|
||||
* and forwards the supported-version matrix.
|
||||
*/
|
||||
export const resolveConfig = (raw: RawConfig, kind: Kind, matrix: Matrix): ResolvedConfig => {
|
||||
if (kind === 'store') return resolveStoreConfig(raw, matrix);
|
||||
return resolveExtensionConfig(raw, matrix);
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
import { isTier, servicesForTiers, TIER_TO_SERVICES } from './tier-map';
|
||||
|
||||
describe('isTier', () => {
|
||||
it('accepts every key in TIER_TO_SERVICES', () => {
|
||||
for (const tier of Object.keys(TIER_TO_SERVICES)) {
|
||||
expect(isTier(tier)).toBe(true);
|
||||
}
|
||||
});
|
||||
|
||||
it('rejects unknown strings', () => {
|
||||
expect(isTier('llm')).toBe(false);
|
||||
expect(isTier('')).toBe(false);
|
||||
});
|
||||
|
||||
it('rejects non-strings', () => {
|
||||
expect(isTier(42)).toBe(false);
|
||||
expect(isTier(null)).toBe(false);
|
||||
expect(isTier(undefined)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('servicesForTiers', () => {
|
||||
it('returns an empty set for an empty tier list', () => {
|
||||
expect([...servicesForTiers([])]).toEqual([]);
|
||||
});
|
||||
|
||||
it('expands a single tier to its concrete service names', () => {
|
||||
expect([...servicesForTiers(['queue'])]).toEqual(['rabbitmq']);
|
||||
});
|
||||
|
||||
it('expands the search tier to both implementations', () => {
|
||||
expect([...servicesForTiers(['search'])].sort()).toEqual(['elasticsearch', 'opensearch']);
|
||||
});
|
||||
|
||||
it('expands the web tier to nginx + php-fpm', () => {
|
||||
expect([...servicesForTiers(['web'])].sort()).toEqual(['nginx', 'php-fpm']);
|
||||
});
|
||||
|
||||
it('unions across multiple tiers', () => {
|
||||
expect([...servicesForTiers(['cache', 'queue'])].sort()).toEqual(['rabbitmq', 'redis', 'valkey']);
|
||||
});
|
||||
|
||||
it('deduplicates if the same tier appears twice', () => {
|
||||
expect([...servicesForTiers(['queue', 'queue'])]).toEqual(['rabbitmq']);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,39 @@
|
||||
/**
|
||||
* A category of services that can be selected for a job. Mirrors the
|
||||
* `Tier` concept in `supported-version` but adds the `web` tier so the
|
||||
* smoke-test and integration jobs can opt the nginx + php-fpm pair in
|
||||
* or out as a unit. Intentionally narrower than supported-version's
|
||||
* tier set: this file only names tiers that the resolve-check-config
|
||||
* schema lets callers toggle.
|
||||
*/
|
||||
export type Tier = 'db' | 'search' | 'queue' | 'cache' | 'web';
|
||||
|
||||
/**
|
||||
* Expansion of each tier to the concrete service names that may
|
||||
* appear in a supported-version matrix entry's `services` map.
|
||||
* Filtering a matrix entry's services for a tier list means keeping
|
||||
* the keys that union to the values of the selected tiers here.
|
||||
*/
|
||||
export const TIER_TO_SERVICES: Record<Tier, readonly string[]> = {
|
||||
db: ['mysql'],
|
||||
search: ['opensearch', 'elasticsearch'],
|
||||
queue: ['rabbitmq'],
|
||||
cache: ['valkey', 'redis'],
|
||||
web: ['nginx', 'php-fpm'],
|
||||
};
|
||||
|
||||
export const isTier = (value: unknown): value is Tier =>
|
||||
typeof value === 'string' && value in TIER_TO_SERVICES;
|
||||
|
||||
/**
|
||||
* Returns the flat set of concrete service names for a list of tiers.
|
||||
* Used to filter a matrix entry's `services` map down to only the
|
||||
* containers a particular job actually needs.
|
||||
*/
|
||||
export const servicesForTiers = (tiers: readonly Tier[]): Set<string> => {
|
||||
const result = new Set<string>();
|
||||
for (const tier of tiers) {
|
||||
for (const name of TIER_TO_SERVICES[tier]) result.add(name);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
import { Tier } from './tier-map';
|
||||
import { Probe } from './probe';
|
||||
|
||||
/**
|
||||
* Which reusable workflow a config belongs to. Selects the known-job
|
||||
* list used for validation and the default config path.
|
||||
*/
|
||||
export type Kind = 'store' | 'extension';
|
||||
|
||||
/**
|
||||
* A single service container definition from supported-version's
|
||||
* matrix output. We don't model the inner shape here — we just
|
||||
* preserve unknown keys when filtering.
|
||||
*/
|
||||
export interface ServiceConfig {
|
||||
image: string;
|
||||
env?: Record<string, string>;
|
||||
ports?: string[];
|
||||
options?: string;
|
||||
volumes?: string[];
|
||||
}
|
||||
|
||||
export interface Services {
|
||||
[serviceName: string]: ServiceConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* One row of supported-version's matrix. Carries the PHP/Composer/etc
|
||||
* coordinates plus the concrete `services` map this job should bring
|
||||
* up. We type the known fields supported-version emits and allow
|
||||
* extras to pass through untouched.
|
||||
*/
|
||||
export interface MatrixEntry {
|
||||
services?: Services;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
/**
|
||||
* The matrix shape emitted by supported-version, suitable for
|
||||
* `strategy.matrix` in GitHub Actions.
|
||||
*/
|
||||
export interface Matrix {
|
||||
include: MatrixEntry[];
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
/**
|
||||
* Per-job static defaults declared by each kind module.
|
||||
*
|
||||
* `services` is the tier list used when the caller's config does not
|
||||
* override it — these tiers are user-toggleable through the schema.
|
||||
*
|
||||
* `requiredServices` is always merged in on top of the resolved list,
|
||||
* regardless of caller overrides. Use it for tiers a job structurally
|
||||
* cannot run without (e.g. mysql for a running store smoke-test) and
|
||||
* which therefore should not appear in the user-facing schema enum.
|
||||
*
|
||||
* `probes` is the default smoke-test probe list used when the caller
|
||||
* does not override it. Only jobs that declare it support the
|
||||
* `probes` config key; omit it for jobs that have no probe concept.
|
||||
*/
|
||||
export interface JobDefaults {
|
||||
services: readonly Tier[];
|
||||
requiredServices?: readonly Tier[];
|
||||
probes?: readonly Probe[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolved per-job output. `enabled` mirrors the input boolean (or
|
||||
* `enabled` key); `matrix` is supported-version's matrix with each
|
||||
* entry's `services` filtered to the tiers this job needs.
|
||||
*/
|
||||
export interface ResolvedJobConfig {
|
||||
enabled: boolean;
|
||||
matrix: Matrix;
|
||||
probes?: Probe[];
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
/**
|
||||
* Map of job-name -> resolved config. Keys are exactly the job names
|
||||
* declared by the kind module (omitted-by-caller jobs still appear,
|
||||
* carrying defaults so the consumer's `if:` guard works uniformly).
|
||||
*/
|
||||
export interface ResolvedConfig {
|
||||
[jobName: string]: ResolvedJobConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shape of a single per-job entry in the user's JSON config file.
|
||||
* - `true` / `false`: shorthand for `{ enabled: true|false }`
|
||||
* - object: explicit enabled flag plus an optional tier list under
|
||||
* `services` and an optional probe list under `probes` (both
|
||||
* validated against the per-kind schema).
|
||||
*/
|
||||
export type RawJobConfig =
|
||||
| boolean
|
||||
| { enabled?: boolean; services?: string[]; probes?: string[]; [key: string]: unknown };
|
||||
|
||||
/**
|
||||
* Top-level shape of the user's JSON config file. Job toggles live
|
||||
* under `jobs`; the rest of the top level is reserved for future
|
||||
* global keys.
|
||||
*/
|
||||
export interface RawConfig {
|
||||
jobs?: { [jobName: string]: RawJobConfig };
|
||||
[key: string]: unknown;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"resolveJsonModule": true,
|
||||
"esModuleInterop": true,
|
||||
"typeRoots": ["../node_modules/@types"],
|
||||
"types": ["jest"]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
# Sansec eComscan Security Scan Action
|
||||
|
||||
A Github Action that runs the [Sansec eComscan](https://sansec.io/ecomscan) security scanner.
|
||||
|
||||
## Inputs
|
||||
|
||||
See the [action.yml](./action.yml)
|
||||
|
||||
## Usage
|
||||
|
||||
The caller is responsible for checking out the repository before calling this action. A valid Sansec license key must be passed via the `ecomscan_key` input.
|
||||
|
||||
The `path` input should point to the root of the Magento installation — the directory that contains `app/`, `vendor/`, etc. It defaults to `.` (the current working directory).
|
||||
|
||||
```yml
|
||||
name: Sansec eComscan Security Scan
|
||||
|
||||
on:
|
||||
push:
|
||||
pull_request_target:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
run-ecomscan:
|
||||
# Skip if it's a push event on a PR (it can't access secrets)
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- uses: graycoreio/github-actions-magento2/sansec-ecomscan@v8.5.0 # x-release-please-version
|
||||
with:
|
||||
license: ${{ secrets.SANSEC_LICENSE_KEY }}
|
||||
```
|
||||
@@ -0,0 +1,53 @@
|
||||
name: "Sansec eComscan Security Scan"
|
||||
author: "Graycore"
|
||||
description: "A Github Action that runs the Sansec eComscan security scanner."
|
||||
|
||||
inputs:
|
||||
license:
|
||||
required: true
|
||||
description: "Sansec license key (ECOMSCAN_KEY)"
|
||||
|
||||
path:
|
||||
required: true
|
||||
default: '.'
|
||||
description: "The directory to scan."
|
||||
|
||||
skip_database:
|
||||
required: false
|
||||
default: 'true'
|
||||
description: "Skip the database scan (--skip-database). Defaults to true."
|
||||
|
||||
skip-server-checks:
|
||||
required: false
|
||||
default: 'true'
|
||||
description: "Skip server / os level checks like copy-fail"
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Download eComscan
|
||||
shell: bash
|
||||
run: wget https://ecomscan.com/downloads/linux-amd64/ecomscan
|
||||
|
||||
- name: Fix permissions
|
||||
shell: bash
|
||||
run: chmod +x ecomscan
|
||||
|
||||
- name: Run eComscan
|
||||
shell: bash
|
||||
env:
|
||||
ECOMSCAN_KEY: ${{ inputs.license }}
|
||||
run: |
|
||||
[ "${{ inputs.skip-server-checks }}" = "true" ] && export ECOMSCAN_SKIP_SERVER_CHECKS=true
|
||||
FLAGS=(--no-auto-update --deep --format=csv)
|
||||
[ "${{ inputs.skip_database }}" = "true" ] && FLAGS+=(--skip-database)
|
||||
output=$(./ecomscan "${FLAGS[@]}" "${{ inputs.path }}")
|
||||
if [ -n "$output" ]; then
|
||||
echo "Security issues found:"
|
||||
echo "$output"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
branding:
|
||||
icon: "shield"
|
||||
color: "red"
|
||||
@@ -31,7 +31,7 @@ jobs:
|
||||
name: A job to semantically compare two versions
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: graycoreio/github-actions-magento2/semver-compare@main
|
||||
- uses: graycoreio/github-actions-magento2/semver-compare@v8.5.0 # x-release-please-version
|
||||
with:
|
||||
version: 2.1.0
|
||||
compare_against: 2.2.3
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
# Setup DI Compile
|
||||
|
||||
A GitHub Action that enables all Magento modules and runs `bin/magento setup:di:compile`.
|
||||
|
||||
The caller is responsible for checkout, PHP/composer setup, and `composer install`. This action only performs the compilation step.
|
||||
|
||||
## Inputs
|
||||
|
||||
| Input | Required | Default | Description |
|
||||
| ------ | -------- | ------- | ------------------------------------------------------------------ |
|
||||
| `path` | No | `.` | Path to the Magento root directory. |
|
||||
|
||||
## Usage
|
||||
|
||||
```yml
|
||||
name: DI Compile
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
branches: [main]
|
||||
|
||||
jobs:
|
||||
compute_matrix:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
matrix: ${{ steps.supported-version.outputs.matrix }}
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: graycoreio/github-actions-magento2/supported-version@v8.5.0 # x-release-please-version
|
||||
id: supported-version
|
||||
|
||||
compile:
|
||||
needs: compute_matrix
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix: ${{ fromJSON(needs.compute_matrix.outputs.matrix) }}
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- uses: graycoreio/github-actions-magento2/setup-magento@v8.5.0 # x-release-please-version
|
||||
id: setup-magento
|
||||
with:
|
||||
php-version: ${{ matrix.php }}
|
||||
tools: composer:v${{ matrix.composer }}
|
||||
|
||||
- uses: graycoreio/github-actions-magento2/cache-magento@v8.5.0 # x-release-please-version
|
||||
|
||||
- run: composer install
|
||||
env:
|
||||
COMPOSER_AUTH: ${{ secrets.COMPOSER_AUTH }}
|
||||
|
||||
- uses: graycoreio/github-actions-magento2/setup-di-compile@v8.5.0 # x-release-please-version
|
||||
with:
|
||||
path: ${{ steps.setup-magento.outputs.path }}
|
||||
```
|
||||
@@ -0,0 +1,26 @@
|
||||
name: "Magento compilation (setup:di:compile)"
|
||||
author: "Graycore"
|
||||
description: "A GitHub Action that runs bin/magento setup:di:compile."
|
||||
|
||||
inputs:
|
||||
path:
|
||||
required: false
|
||||
default: "."
|
||||
description: "Path to the Magento root directory. Accepts the output of the setup-magento action."
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Enable all modules
|
||||
working-directory: ${{ inputs.path }}
|
||||
shell: bash
|
||||
run: php bin/magento module:enable --all
|
||||
|
||||
- name: Compile
|
||||
working-directory: ${{ inputs.path }}
|
||||
shell: bash
|
||||
run: php bin/magento setup:di:compile
|
||||
|
||||
branding:
|
||||
icon: "tool"
|
||||
color: "orange"
|
||||
@@ -0,0 +1,71 @@
|
||||
# Setup Install
|
||||
|
||||
A GitHub Action that runs `bin/magento setup:install` using the services configuration from the [`supported-version`](../supported-version/README.md) matrix output. Database and service credentials are read directly from the service configuration of `supported-version`, so they stay in sync with the running containers automatically.
|
||||
|
||||
## Inputs
|
||||
|
||||
| Input | Required | Default | Description |
|
||||
| ------------ | -------- | ------- | ----------------------------------------------------------- |
|
||||
| `services` | No | `null` | JSON services object from `supported-version` matrix output |
|
||||
| `path` | No | `.` | Path to the Magento root directory |
|
||||
| `extra_args` | No | `""` | Additional arguments to append to `setup:install` |
|
||||
|
||||
## Outputs
|
||||
|
||||
| Output | Description |
|
||||
| --------- | --------------------------------------------------------- |
|
||||
| `command` | The full `bin/magento setup:install` command that was run |
|
||||
|
||||
## Usage
|
||||
|
||||
This action is designed to work with [`supported-version`](../supported-version/README.md) (with `include_services: true`) and [`setup-magento`](../setup-magento/README.md).
|
||||
|
||||
```yml
|
||||
name: Install Test
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
branches: [main]
|
||||
|
||||
jobs:
|
||||
compute_matrix:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
matrix: ${{ steps.supported-version.outputs.matrix }}
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: graycoreio/github-actions-magento2/supported-version@v8.5.0 # x-release-please-version
|
||||
id: supported-version
|
||||
with:
|
||||
include_services: "true"
|
||||
|
||||
install:
|
||||
needs: compute_matrix
|
||||
runs-on: ${{ matrix.os }}
|
||||
services: ${{ matrix.services }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix: ${{ fromJSON(needs.compute_matrix.outputs.matrix) }}
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- uses: graycoreio/github-actions-magento2/setup-magento@v8.5.0 # x-release-please-version
|
||||
id: setup-magento
|
||||
with:
|
||||
php-version: ${{ matrix.php }}
|
||||
tools: composer:v${{ matrix.composer }}
|
||||
env:
|
||||
COMPOSER_AUTH: ${{ secrets.COMPOSER_AUTH }}
|
||||
|
||||
- run: composer install
|
||||
working-directory: ${{ steps.setup-magento.outputs.path }}
|
||||
env:
|
||||
COMPOSER_AUTH: ${{ secrets.COMPOSER_AUTH }}
|
||||
|
||||
- uses: graycoreio/github-actions-magento2/setup-install@v8.5.0 # x-release-please-version
|
||||
with:
|
||||
services: ${{ toJSON(matrix.services) }}
|
||||
path: ${{ steps.setup-magento.outputs.path }}
|
||||
```
|
||||
@@ -0,0 +1,36 @@
|
||||
name: "Magento setup:install"
|
||||
author: "Graycore"
|
||||
description: "A GitHub Action that runs bin/magento setup:install, deriving service flags from the supported-version services matrix."
|
||||
|
||||
inputs:
|
||||
services:
|
||||
required: false
|
||||
default: "null"
|
||||
description: "JSON string of the services key from the supported-version matrix entry (toJSON(matrix.services))."
|
||||
|
||||
path:
|
||||
required: false
|
||||
default: "."
|
||||
description: "Path to the Magento root directory. Accepts the output of the setup-magento action."
|
||||
|
||||
extra_args:
|
||||
required: false
|
||||
default: ""
|
||||
description: "Additional raw flags to append to the setup:install command."
|
||||
|
||||
container_id:
|
||||
required: false
|
||||
default: ""
|
||||
description: "When set, runs setup:install via `docker exec` inside the container with this `container_id` (typically the value of `job.services['php-fpm'].id`) and uses service-network aliases (mysql, redis, etc.) instead of runner-side localhost when running setup:install."
|
||||
|
||||
outputs:
|
||||
command:
|
||||
description: "The full bin/magento setup:install command that was run."
|
||||
|
||||
runs:
|
||||
using: "node24"
|
||||
main: "dist/index.js"
|
||||
|
||||
branding:
|
||||
icon: "tool"
|
||||
color: "orange"
|
||||
Vendored
+69
File diff suppressed because one or more lines are too long
@@ -0,0 +1,9 @@
|
||||
module.exports = {
|
||||
clearMocks: true,
|
||||
moduleFileExtensions: ['js', 'ts'],
|
||||
testMatch: ['**/*.spec.ts'],
|
||||
transform: {
|
||||
'^.+\\.ts$': 'ts-jest'
|
||||
},
|
||||
verbose: true
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"name": "@graycoreio/github-actions-magento2-setup-install",
|
||||
"version": "1.0.0",
|
||||
"description": "A Github Action that runs bin/magento setup:install from the supported-version services matrix",
|
||||
"main": "index.js",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "npx esbuild --outfile=dist/index.js --platform=node --bundle --minify src/index.ts",
|
||||
"test": "jest"
|
||||
},
|
||||
"author": "",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@actions/core": "0.0.0-PLACEHOLDER",
|
||||
"@actions/exec": "0.0.0-PLACEHOLDER"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jest": "0.0.0-PLACEHOLDER",
|
||||
"jest": "0.0.0-PLACEHOLDER",
|
||||
"ts-jest": "0.0.0-PLACEHOLDER"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,277 @@
|
||||
import { buildInstallArgs, buildMysqlPrepArgs, Services } from './build-command';
|
||||
|
||||
const BASE_ARGS = [
|
||||
'--base-url=http://localhost/',
|
||||
'--admin-user=admin',
|
||||
'--admin-password=admin123',
|
||||
'--admin-email=admin@example.com',
|
||||
'--admin-firstname=Admin',
|
||||
'--admin-lastname=User',
|
||||
'--backend-frontname=admin',
|
||||
'--no-interaction',
|
||||
];
|
||||
|
||||
const MYSQL_SERVICE = {
|
||||
image: 'mysql:8.4',
|
||||
env: {
|
||||
MYSQL_DATABASE: 'magento_integration_tests',
|
||||
MYSQL_USER: 'user',
|
||||
MYSQL_PASSWORD: 'password',
|
||||
MYSQL_ROOT_PASSWORD: 'rootpassword',
|
||||
},
|
||||
ports: ['3306:3306'],
|
||||
options: '--health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3',
|
||||
};
|
||||
|
||||
const MYSQL_ARGS = [
|
||||
'--db-host=127.0.0.1:3306',
|
||||
'--db-name=magento_integration_tests',
|
||||
'--db-user=user',
|
||||
'--db-password=password',
|
||||
];
|
||||
|
||||
const OPENSEARCH_ARGS = [
|
||||
'--search-engine=opensearch',
|
||||
'--opensearch-host=localhost',
|
||||
'--opensearch-port=9200',
|
||||
];
|
||||
|
||||
const RABBITMQ_ARGS = [
|
||||
'--amqp-host=localhost',
|
||||
'--amqp-port=5672',
|
||||
'--amqp-user=guest',
|
||||
'--amqp-password=guest',
|
||||
];
|
||||
|
||||
const CACHE_ARGS = [
|
||||
'--session-save=redis',
|
||||
'--session-save-redis-host=localhost',
|
||||
'--session-save-redis-port=6379',
|
||||
'--cache-backend=redis',
|
||||
'--cache-backend-redis-server=localhost',
|
||||
'--cache-backend-redis-port=6379',
|
||||
];
|
||||
|
||||
describe('buildInstallArgs', () => {
|
||||
describe('base args', () => {
|
||||
it('returns only base args when services is null', () => {
|
||||
expect(buildInstallArgs(null)).toEqual(BASE_ARGS);
|
||||
});
|
||||
|
||||
it('returns only base args when services is empty', () => {
|
||||
expect(buildInstallArgs({})).toEqual(BASE_ARGS);
|
||||
});
|
||||
|
||||
it('runs non-interactively', () => {
|
||||
expect(buildInstallArgs(null)).toContain('--no-interaction');
|
||||
});
|
||||
});
|
||||
|
||||
describe('mysql', () => {
|
||||
it('adds db flags when mysql service is present', () => {
|
||||
const services: Services = { mysql: MYSQL_SERVICE };
|
||||
expect(buildInstallArgs(services)).toEqual([...BASE_ARGS, ...MYSQL_ARGS]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('search engine', () => {
|
||||
it('adds opensearch flags when opensearch service is present', () => {
|
||||
const services: Services = { opensearch: { image: 'opensearchproject/opensearch:2.19.1' } };
|
||||
expect(buildInstallArgs(services)).toEqual([...BASE_ARGS, ...OPENSEARCH_ARGS]);
|
||||
});
|
||||
|
||||
it('adds elasticsearch7 flags for an elasticsearch 7.x image', () => {
|
||||
const services: Services = { elasticsearch: { image: 'elasticsearch:7.17.0' } };
|
||||
expect(buildInstallArgs(services)).toEqual([
|
||||
...BASE_ARGS,
|
||||
'--search-engine=elasticsearch7',
|
||||
'--elasticsearch-host=localhost',
|
||||
'--elasticsearch-port=9200',
|
||||
]);
|
||||
});
|
||||
|
||||
it('adds elasticsearch8 flags for an elasticsearch 8.x image', () => {
|
||||
const services: Services = { elasticsearch: { image: 'elasticsearch:8.11.4' } };
|
||||
expect(buildInstallArgs(services)).toEqual([
|
||||
...BASE_ARGS,
|
||||
'--search-engine=elasticsearch8',
|
||||
'--elasticsearch-host=localhost',
|
||||
'--elasticsearch-port=9200',
|
||||
]);
|
||||
});
|
||||
|
||||
it('prefers opensearch over elasticsearch when both are present', () => {
|
||||
const services: Services = {
|
||||
opensearch: { image: 'opensearchproject/opensearch:2.19.1' },
|
||||
elasticsearch: { image: 'elasticsearch:8.11.4' },
|
||||
};
|
||||
const args = buildInstallArgs(services);
|
||||
expect(args).toContain('--search-engine=opensearch');
|
||||
expect(args.some(a => a.startsWith('--search-engine=elasticsearch'))).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('rabbitmq', () => {
|
||||
it('adds amqp flags when rabbitmq service is present', () => {
|
||||
const services: Services = { rabbitmq: { image: 'rabbitmq:4.0-management' } };
|
||||
expect(buildInstallArgs(services)).toEqual([...BASE_ARGS, ...RABBITMQ_ARGS]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('cache / session', () => {
|
||||
it('adds redis cache flags when redis service is present', () => {
|
||||
const services: Services = { redis: { image: 'redis:7.2' } };
|
||||
expect(buildInstallArgs(services)).toEqual([...BASE_ARGS, ...CACHE_ARGS]);
|
||||
});
|
||||
|
||||
it('adds redis cache flags when valkey service is present', () => {
|
||||
const services: Services = { valkey: { image: 'valkey:8.0' } };
|
||||
expect(buildInstallArgs(services)).toEqual([...BASE_ARGS, ...CACHE_ARGS]);
|
||||
});
|
||||
|
||||
it('adds cache flags once when both valkey and redis are present', () => {
|
||||
const services: Services = {
|
||||
valkey: { image: 'valkey:8.0' },
|
||||
redis: { image: 'redis:7.2' },
|
||||
};
|
||||
const args = buildInstallArgs(services);
|
||||
expect(args.filter(a => a === '--session-save=redis')).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('buildMysqlPrepArgs', () => {
|
||||
it('uses root password and port from service config', () => {
|
||||
expect(buildMysqlPrepArgs(MYSQL_SERVICE)).toEqual([
|
||||
'-h127.0.0.1',
|
||||
'--port=3306',
|
||||
'-uroot',
|
||||
'-prootpassword',
|
||||
'-e', 'SET GLOBAL log_bin_trust_function_creators = 1;',
|
||||
]);
|
||||
});
|
||||
|
||||
it('falls back to default port when ports is absent', () => {
|
||||
const args = buildMysqlPrepArgs({ image: 'mysql:8.4' });
|
||||
expect(args).toContain('--port=3306');
|
||||
});
|
||||
|
||||
it('falls back to default root password when env is absent', () => {
|
||||
const args = buildMysqlPrepArgs({ image: 'mysql:8.4' });
|
||||
expect(args).toContain('-prootpassword');
|
||||
});
|
||||
});
|
||||
|
||||
describe('all services', () => {
|
||||
it('adds all flags when all services are present', () => {
|
||||
const services: Services = {
|
||||
mysql: MYSQL_SERVICE,
|
||||
opensearch: { image: 'opensearchproject/opensearch:2.19.1' },
|
||||
rabbitmq: { image: 'rabbitmq:4.0-management' },
|
||||
valkey: { image: 'valkey:8.0' },
|
||||
};
|
||||
expect(buildInstallArgs(services)).toEqual([
|
||||
...BASE_ARGS,
|
||||
...MYSQL_ARGS,
|
||||
...OPENSEARCH_ARGS,
|
||||
...RABBITMQ_ARGS,
|
||||
...CACHE_ARGS,
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('container mode', () => {
|
||||
it('uses the mysql network alias and the internal port from ports[0]', () => {
|
||||
const services: Services = {
|
||||
mysql: { ...MYSQL_SERVICE, ports: ['33060:3306'] },
|
||||
};
|
||||
expect(buildInstallArgs(services, true)).toEqual([
|
||||
...BASE_ARGS,
|
||||
'--db-host=mysql:3306',
|
||||
'--db-name=magento_integration_tests',
|
||||
'--db-user=user',
|
||||
'--db-password=password',
|
||||
]);
|
||||
});
|
||||
|
||||
it('uses the opensearch alias and parses the internal port', () => {
|
||||
const services: Services = {
|
||||
opensearch: { image: 'opensearchproject/opensearch:2.19.1', ports: ['19200:9200'] },
|
||||
};
|
||||
expect(buildInstallArgs(services, true)).toEqual([
|
||||
...BASE_ARGS,
|
||||
'--search-engine=opensearch',
|
||||
'--opensearch-host=opensearch',
|
||||
'--opensearch-port=9200',
|
||||
]);
|
||||
});
|
||||
|
||||
it('uses the elasticsearch alias and parses the internal port', () => {
|
||||
const services: Services = {
|
||||
elasticsearch: { image: 'elasticsearch:8.11.4', ports: ['19200:9200'] },
|
||||
};
|
||||
expect(buildInstallArgs(services, true)).toEqual([
|
||||
...BASE_ARGS,
|
||||
'--search-engine=elasticsearch8',
|
||||
'--elasticsearch-host=elasticsearch',
|
||||
'--elasticsearch-port=9200',
|
||||
]);
|
||||
});
|
||||
|
||||
it('uses the rabbitmq alias and parses the internal port', () => {
|
||||
const services: Services = {
|
||||
rabbitmq: { image: 'rabbitmq:4.0-management', ports: ['15672:5672'] },
|
||||
};
|
||||
expect(buildInstallArgs(services, true)).toEqual([
|
||||
...BASE_ARGS,
|
||||
'--amqp-host=rabbitmq',
|
||||
'--amqp-port=5672',
|
||||
'--amqp-user=guest',
|
||||
'--amqp-password=guest',
|
||||
]);
|
||||
});
|
||||
|
||||
it('uses the valkey alias when valkey is the cache service', () => {
|
||||
const services: Services = {
|
||||
valkey: { image: 'valkey:8.0', ports: ['16379:6379'] },
|
||||
};
|
||||
expect(buildInstallArgs(services, true)).toEqual([
|
||||
...BASE_ARGS,
|
||||
'--session-save=redis',
|
||||
'--session-save-redis-host=valkey',
|
||||
'--session-save-redis-port=6379',
|
||||
'--cache-backend=redis',
|
||||
'--cache-backend-redis-server=valkey',
|
||||
'--cache-backend-redis-port=6379',
|
||||
]);
|
||||
});
|
||||
|
||||
it('uses the redis alias when redis is the cache service', () => {
|
||||
const services: Services = {
|
||||
redis: { image: 'redis:7.2', ports: ['16379:6379'] },
|
||||
};
|
||||
expect(buildInstallArgs(services, true)).toEqual([
|
||||
...BASE_ARGS,
|
||||
'--session-save=redis',
|
||||
'--session-save-redis-host=redis',
|
||||
'--session-save-redis-port=6379',
|
||||
'--cache-backend=redis',
|
||||
'--cache-backend-redis-server=redis',
|
||||
'--cache-backend-redis-port=6379',
|
||||
]);
|
||||
});
|
||||
|
||||
it('falls back to default internal ports when ports are absent', () => {
|
||||
const services: Services = {
|
||||
mysql: { ...MYSQL_SERVICE, ports: undefined },
|
||||
opensearch: { image: 'opensearchproject/opensearch:2.19.1' },
|
||||
rabbitmq: { image: 'rabbitmq:4.0-management' },
|
||||
valkey: { image: 'valkey:8.0' },
|
||||
};
|
||||
const args = buildInstallArgs(services, true);
|
||||
expect(args).toContain('--db-host=mysql:3306');
|
||||
expect(args).toContain('--opensearch-port=9200');
|
||||
expect(args).toContain('--amqp-port=5672');
|
||||
expect(args).toContain('--session-save-redis-port=6379');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,102 @@
|
||||
export interface ServiceConfig {
|
||||
image: string;
|
||||
env?: Record<string, string>;
|
||||
ports?: string[];
|
||||
options?: string;
|
||||
volumes?: string[];
|
||||
}
|
||||
|
||||
export interface Services {
|
||||
mysql?: ServiceConfig;
|
||||
opensearch?: ServiceConfig;
|
||||
elasticsearch?: ServiceConfig;
|
||||
rabbitmq?: ServiceConfig;
|
||||
redis?: ServiceConfig;
|
||||
valkey?: ServiceConfig;
|
||||
'php-fpm'?: ServiceConfig;
|
||||
}
|
||||
|
||||
const BASE_ARGS = [
|
||||
'--base-url=http://localhost/',
|
||||
'--admin-user=admin',
|
||||
'--admin-password=admin123',
|
||||
'--admin-email=admin@example.com',
|
||||
'--admin-firstname=Admin',
|
||||
'--admin-lastname=User',
|
||||
'--backend-frontname=admin',
|
||||
'--no-interaction',
|
||||
];
|
||||
|
||||
const parsePort = (svc: ServiceConfig | undefined, index: 0 | 1, fallback: string): string => {
|
||||
return svc?.ports?.[0]?.split(':')[index] ?? fallback;
|
||||
};
|
||||
|
||||
export const buildMysqlPrepArgs = (mysql: ServiceConfig): string[] => {
|
||||
const rootPassword = mysql.env?.MYSQL_ROOT_PASSWORD ?? 'rootpassword';
|
||||
const port = parsePort(mysql, 0, '3306');
|
||||
return ['-h127.0.0.1', `--port=${port}`, '-uroot', `-p${rootPassword}`, '-e', 'SET GLOBAL log_bin_trust_function_creators = 1;'];
|
||||
};
|
||||
|
||||
export const buildInstallArgs = (services: Services | null, containerMode = false): string[] => {
|
||||
const args = [...BASE_ARGS];
|
||||
|
||||
if (!services) return args;
|
||||
|
||||
const portIdx: 0 | 1 = containerMode ? 1 : 0;
|
||||
const hostFor = (alias: string): string => containerMode ? alias : 'localhost';
|
||||
|
||||
if (services.mysql) {
|
||||
const dbPort = parsePort(services.mysql, portIdx, '3306');
|
||||
const dbHost = containerMode ? `mysql:${dbPort}` : `127.0.0.1:${dbPort}`;
|
||||
args.push(
|
||||
`--db-host=${dbHost}`,
|
||||
`--db-name=${services.mysql.env?.MYSQL_DATABASE ?? 'magento'}`,
|
||||
`--db-user=${services.mysql.env?.MYSQL_USER ?? 'magento'}`,
|
||||
`--db-password=${services.mysql.env?.MYSQL_PASSWORD ?? 'magento'}`,
|
||||
);
|
||||
}
|
||||
|
||||
if (services.opensearch) {
|
||||
const port = parsePort(services.opensearch, portIdx, '9200');
|
||||
args.push(
|
||||
'--search-engine=opensearch',
|
||||
`--opensearch-host=${hostFor('opensearch')}`,
|
||||
`--opensearch-port=${port}`,
|
||||
);
|
||||
} else if (services.elasticsearch) {
|
||||
const majorVersion = services.elasticsearch.image.split(':')[1]?.split('.')[0];
|
||||
const port = parsePort(services.elasticsearch, portIdx, '9200');
|
||||
args.push(
|
||||
`--search-engine=elasticsearch${majorVersion}`,
|
||||
`--elasticsearch-host=${hostFor('elasticsearch')}`,
|
||||
`--elasticsearch-port=${port}`,
|
||||
);
|
||||
}
|
||||
|
||||
if (services.rabbitmq) {
|
||||
const port = parsePort(services.rabbitmq, portIdx, '5672');
|
||||
args.push(
|
||||
`--amqp-host=${hostFor('rabbitmq')}`,
|
||||
`--amqp-port=${port}`,
|
||||
'--amqp-user=guest',
|
||||
'--amqp-password=guest',
|
||||
);
|
||||
}
|
||||
|
||||
if (services.valkey || services.redis) {
|
||||
const cacheKey: 'valkey' | 'redis' = services.valkey ? 'valkey' : 'redis';
|
||||
const cache = services[cacheKey]!;
|
||||
const port = parsePort(cache, portIdx, '6379');
|
||||
const host = hostFor(cacheKey);
|
||||
args.push(
|
||||
'--session-save=redis',
|
||||
`--session-save-redis-host=${host}`,
|
||||
`--session-save-redis-port=${port}`,
|
||||
'--cache-backend=redis',
|
||||
`--cache-backend-redis-server=${host}`,
|
||||
`--cache-backend-redis-port=${port}`,
|
||||
);
|
||||
}
|
||||
|
||||
return args;
|
||||
};
|
||||
@@ -0,0 +1,71 @@
|
||||
import * as core from '@actions/core';
|
||||
import * as exec from '@actions/exec';
|
||||
import * as nodePath from 'path';
|
||||
import { buildInstallArgs, buildMysqlPrepArgs, Services } from './build-command';
|
||||
|
||||
const resolveContainerPath = (runnerPath: string): string => {
|
||||
const workspace = process.env.GITHUB_WORKSPACE || '';
|
||||
const absolute = nodePath.resolve(workspace, runnerPath);
|
||||
const relative = nodePath.relative(workspace, absolute);
|
||||
if (relative.startsWith('..')) {
|
||||
throw new Error(`container_id: path ${runnerPath} resolves outside GITHUB_WORKSPACE (${workspace})`);
|
||||
}
|
||||
return relative ? `/var/www/html/${relative}` : '/var/www/html';
|
||||
};
|
||||
|
||||
export async function run(): Promise<void> {
|
||||
try {
|
||||
const servicesInput = core.getInput('services');
|
||||
const path = core.getInput('path') || '.';
|
||||
const extraArgs = core.getInput('extra_args').trim();
|
||||
const containerId = core.getInput('container_id').trim();
|
||||
const containerMode = containerId !== '';
|
||||
|
||||
let services: Services | null = null;
|
||||
if (servicesInput && servicesInput !== 'null') {
|
||||
services = JSON.parse(servicesInput) as Services;
|
||||
}
|
||||
|
||||
// setup:install creates MySQL triggers, which requires log_bin_trust_function_creators=1
|
||||
// when binary logging is enabled. The prep always runs runner-side against the published port.
|
||||
if (services?.mysql) {
|
||||
await exec.exec('mysql', buildMysqlPrepArgs(services.mysql));
|
||||
}
|
||||
|
||||
const args = buildInstallArgs(services, containerMode);
|
||||
|
||||
if (extraArgs) {
|
||||
args.push(...extraArgs.split(/\s+/));
|
||||
}
|
||||
|
||||
if (containerMode) {
|
||||
const containerPath = resolveContainerPath(path);
|
||||
|
||||
const command = `docker exec -w ${containerPath} ${containerId} php bin/magento setup:install ${args.join(' ')}`;
|
||||
core.setOutput('command', command);
|
||||
|
||||
await exec.exec('docker', [
|
||||
'exec',
|
||||
'-w', containerPath,
|
||||
containerId,
|
||||
'php', 'bin/magento', 'setup:install',
|
||||
...args,
|
||||
]);
|
||||
|
||||
// setup:install runs as root inside the container, but php-fpm workers
|
||||
// serve requests as `www-data`. Hand ownership of the Magento writable
|
||||
// dirs to www-data so request-time cache/log writes succeed.
|
||||
await exec.exec('docker', [
|
||||
'exec', containerId, 'sh', '-c',
|
||||
`for d in var generated pub/static pub/media; do [ -d "${containerPath}/$d" ] && chown -R www-data:www-data "${containerPath}/$d"; done`,
|
||||
]);
|
||||
} else {
|
||||
core.setOutput('command', `php bin/magento setup:install ${args.join(' ')}`);
|
||||
await exec.exec('php', ['bin/magento', 'setup:install', ...args], { cwd: path });
|
||||
}
|
||||
} catch (error) {
|
||||
core.setFailed((error as Error).message);
|
||||
}
|
||||
}
|
||||
|
||||
run();
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"resolveJsonModule": true,
|
||||
"esModuleInterop": true,
|
||||
"typeRoots": ["../node_modules/@types"],
|
||||
"types": ["jest"]
|
||||
}
|
||||
}
|
||||
+17
-17
@@ -11,23 +11,23 @@ The action operates in two modes:
|
||||
|
||||
## Inputs
|
||||
|
||||
| Input | Required | Default | Description |
|
||||
|-------|----------|---------|-------------|
|
||||
| `php-version` | Yes | `8.4` | PHP version to install |
|
||||
| `mode` | Yes | `extension` | Either `extension` or `store` |
|
||||
| `magento_version` | No | `magento/project-community-edition:2.4.8-p3` | Magento version to install (extension mode only) |
|
||||
| `magento_repository` | No | `https://mirror.mage-os.org/` | Composer repository URL for Magento packages |
|
||||
| `tools` | No | - | PHP tools to install globally (e.g., `composer:v2`) |
|
||||
| `extensions` | No | - | Additional PHP extensions to install |
|
||||
| `coverage` | No | - | Code coverage driver (e.g., `xdebug`, `pcov`) |
|
||||
| `working-directory` | No | `.` | Working directory for the action |
|
||||
| `apply_fixes` | No | `false` | Apply Magento installation fixes (always applied in extension mode) |
|
||||
| `composer_auth` | No | - | Composer authentication credentials JSON |
|
||||
| Input | Required | Default | Description |
|
||||
| -------------------- | -------- | -------------------------------------------- | ------------------------------------------------------------------- |
|
||||
| `php-version` | Yes | `8.4` | PHP version to install |
|
||||
| `mode` | Yes | `extension` | Either `extension` or `store` |
|
||||
| `magento_version` | No | `magento/project-community-edition:2.4.8-p3` | Magento version to install (extension mode only) |
|
||||
| `magento_repository` | No | `https://mirror.mage-os.org/` | Composer repository URL for Magento packages |
|
||||
| `tools` | No | - | PHP tools to install globally (e.g., `composer:v2`) |
|
||||
| `extensions` | No | - | Additional PHP extensions to install |
|
||||
| `coverage` | No | - | Code coverage driver (e.g., `xdebug`, `pcov`) |
|
||||
| `working-directory` | No | `.` | Working directory for the action |
|
||||
| `apply_fixes` | No | `false` | Apply Magento installation fixes (always applied in extension mode) |
|
||||
| `composer_auth` | No | - | Composer authentication credentials JSON |
|
||||
|
||||
## Outputs
|
||||
|
||||
| Output | Description |
|
||||
|--------|-------------|
|
||||
| Output | Description |
|
||||
| ------ | --------------------------------------------------- |
|
||||
| `path` | Absolute path to the Magento installation directory |
|
||||
|
||||
## Usage
|
||||
@@ -51,7 +51,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- uses: graycoreio/github-actions-magento/setup-magento@main
|
||||
- uses: graycoreio/github-actions-magento2/setup-magento@v8.5.0 # x-release-please-version
|
||||
id: setup-magento
|
||||
with:
|
||||
php-version: "8.3"
|
||||
@@ -89,7 +89,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- uses: graycoreio/github-actions-magento/setup-magento@main
|
||||
- uses: graycoreio/github-actions-magento2/setup-magento@v8.5.0 # x-release-please-version
|
||||
id: setup-magento
|
||||
with:
|
||||
php-version: "8.3"
|
||||
@@ -107,4 +107,4 @@ jobs:
|
||||
|
||||
- The action uses [shivammathur/setup-php](https://github.com/shivammathur/setup-php) for PHP installation
|
||||
- By default, Magento packages are fetched from the [Mage-OS mirror](https://mirror.mage-os.org/) which doesn't require authentication
|
||||
- For Adobe Commerce or private packages, provide `composer_auth` with your credentials
|
||||
- For Adobe Commerce or private packages, provide `composer_auth` with your credentials
|
||||
|
||||
@@ -68,7 +68,7 @@ runs:
|
||||
- run: |
|
||||
MAGENTO_DIRECTORY=""
|
||||
if [ "${{ inputs.mode }}" = 'extension' ]; then
|
||||
MAGENTO_DIRECTORY="../magento2"
|
||||
MAGENTO_DIRECTORY="_ghamagento"
|
||||
else
|
||||
MAGENTO_DIRECTORY="${{ inputs.working-directory }}"
|
||||
fi
|
||||
@@ -90,7 +90,28 @@ runs:
|
||||
env:
|
||||
COMPOSER_AUTH: ${{ inputs.composer_auth }}
|
||||
|
||||
- uses: graycoreio/github-actions-magento2/fix-magento-install@v7.0.0-rc.0
|
||||
- run: mkdir -p ${{ steps.setup-magento-compute-directory.outputs.MAGENTO_DIRECTORY }}/app/etc
|
||||
shell: bash
|
||||
name: Ensure app/etc exists for magento composer plugin
|
||||
if: inputs.mode == 'extension'
|
||||
|
||||
- name: Prevent Magento install from being mirrored into consumer vendor
|
||||
if: inputs.mode == 'extension'
|
||||
shell: bash
|
||||
working-directory: ${{ inputs.working-directory }}
|
||||
env:
|
||||
MAGENTO_DIRECTORY: ${{ steps.setup-magento-compute-directory.outputs.MAGENTO_DIRECTORY }}
|
||||
run: |
|
||||
line="/$MAGENTO_DIRECTORY export-ignore"
|
||||
if [[ -f .gitattributes ]] && grep -qxF "$line" .gitattributes; then
|
||||
exit 0
|
||||
fi
|
||||
if [[ -s .gitattributes && -n "$(tail -c1 .gitattributes)" ]]; then
|
||||
printf '\n' >> .gitattributes
|
||||
fi
|
||||
printf '%s\n' "$line" >> .gitattributes
|
||||
|
||||
- uses: graycoreio/github-actions-magento2/fix-magento-install@main
|
||||
name: Fix Magento Out of Box Install Issues
|
||||
with:
|
||||
magento_directory: ${{ steps.setup-magento-compute-directory.outputs.MAGENTO_DIRECTORY }}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user