Compare commits

..

67 Commits

Author SHA1 Message Date
Shivam Mathur
7c071dfe9d Bump version to 2.37.1
Update Node.js dependencies
2026-05-14 08:58:36 +05:30
Shivam Mathur
eeef37e059 GHSA-pqwm-q9pv-ph8r - Fix CWE-78 [skip ci] 2026-05-14 08:58:36 +05:30
Shivam Mathur
0dc33069a3 Fix phalcon5 support on Windows 2026-05-14 03:00:30 +05:30
Shivam Mathur
680a983990 Fix phalcon version for PHP 8.0 [skip ci] 2026-05-14 03:00:29 +05:30
Shivam Mathur
694649a4a3 Fix mutable tool cache restore 2026-05-13 19:43:21 +05:30
Shivam Mathur
46a991b6aa Merge pull request #1081 from Pyker/patch-1
Fix composer v2 version in README
2026-05-13 16:22:14 +05:30
Shivam Mathur
7748c24380 GHSA-f9f8-rm49-7jv2: Fix GitHub auth handling for composer in affected versions 2026-05-13 16:19:11 +05:30
Pedro Cunha
ac9c953234 Fix composer v2 version in README 2026-05-13 10:26:15 +01:00
Shivam Mathur
7729e411ec Improve enabling gearman [skip ci] 2026-04-29 01:00:16 +05:30
Shivam Mathur
af2322b95c Fix fallback in Install-PSPackage on Windows 2026-04-26 23:49:26 +05:30
Shivam Mathur
45e37a5311 Update os_releases.csv [skip ci] 2026-04-26 15:12:30 +05:30
Shivam Mathur
690cb7c9d8 Merge pull request #1074 from shivammathur/dependabot/github_actions/develop/codecov/codecov-action-6
Bump codecov/codecov-action from 5 to 6
2026-03-30 17:04:06 +05:30
dependabot[bot]
b2cf73f5fa Bump codecov/codecov-action from 5 to 6
Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 5 to 6.
- [Release notes](https://github.com/codecov/codecov-action/releases)
- [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/codecov/codecov-action/compare/v5...v6)

---
updated-dependencies:
- dependency-name: codecov/codecov-action
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-30 11:19:53 +00:00
Shivam Mathur
64e2975255 Update dependencies 2026-03-29 20:51:51 +05:30
Shivam Mathur
106fd0866c Strip backslash line continuation from extension inputs 2026-03-29 20:46:19 +05:30
Shivam Mathur
44724c9282 Merge pull request #1072 from shivammathur/dependabot/npm_and_yarn/npm_and_yarn-66413a1f6e
Bump picomatch from 2.3.1 to 2.3.2 in the npm_and_yarn group across 1 directory
2026-03-26 07:42:11 +05:30
dependabot[bot]
2fa35d2e4e Bump picomatch in the npm_and_yarn group across 1 directory
Bumps the npm_and_yarn group with 1 update in the / directory: [picomatch](https://github.com/micromatch/picomatch).


Updates `picomatch` from 2.3.1 to 2.3.2
- [Release notes](https://github.com/micromatch/picomatch/releases)
- [Changelog](https://github.com/micromatch/picomatch/blob/master/CHANGELOG.md)
- [Commits](https://github.com/micromatch/picomatch/compare/2.3.1...2.3.2)

---
updated-dependencies:
- dependency-name: picomatch
  dependency-version: 2.3.2
  dependency-type: indirect
  dependency-group: npm_and_yarn
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-26 00:21:48 +00:00
Shivam Mathur
29e04e0a1d Add workflow examples for Drupal and WordPress plugins 2026-03-23 14:33:38 +05:30
Shivam Mathur
ea13793c13 Merge pull request #1071 from shivammathur/dependabot/npm_and_yarn/npm_and_yarn-e5a595f223
Bump flatted from 3.4.1 to 3.4.2 in the npm_and_yarn group across 1 directory
2026-03-22 02:27:26 +05:30
dependabot[bot]
fd580061e0 Bump flatted in the npm_and_yarn group across 1 directory
Bumps the npm_and_yarn group with 1 update in the / directory: [flatted](https://github.com/WebReflection/flatted).


Updates `flatted` from 3.4.1 to 3.4.2
- [Commits](https://github.com/WebReflection/flatted/compare/v3.4.1...v3.4.2)

---
updated-dependencies:
- dependency-name: flatted
  dependency-version: 3.4.2
  dependency-type: indirect
  dependency-group: npm_and_yarn
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-21 20:14:26 +00:00
Shivam Mathur
90d81e2adc Update examples 2026-03-21 21:47:58 +05:30
Shivam Mathur
93cb3149d2 Fix permission for OIDC tokens in publish.yml for trusted publishing [skip ci] 2026-03-15 22:01:57 +05:30
Shivam Mathur
accd6127cb Update dependencies 2026-03-15 21:44:02 +05:30
Shivam Mathur
a5c2146f3f Improve tool cache to not cache persistent urls 2026-03-15 11:36:39 +05:30
Shivam Mathur
6a1d559c57 Fix regression in pecl_http support [skip ci] 2026-03-15 11:18:29 +05:30
Shivam Mathur
076a5e3f0d Update actions in examples [skip ci] 2026-03-15 11:00:48 +05:30
Shivam Mathur
00c8f84a71 Add support for ionCube for PHP 8.5 [skip ci] 2026-03-13 22:21:20 +05:30
Shivam Mathur
c033e31e7c Add support for zephir parser for PHP 8.5 [skip ci] 2026-03-13 22:10:53 +05:30
Shivam Mathur
bc6f40a11a Switch to downloads.php.net in Get-ICUUrl [skip ci] 2026-03-13 21:33:26 +05:30
Shivam Mathur
fe26b509d7 Fix support for pecl_http on Windows [skip ci] 2026-03-13 20:50:10 +05:30
Shivam Mathur
e67ff94e49 Fix caching couchbase on macOS [skip ci] 2026-03-13 20:15:35 +05:30
Shivam Mathur
4e89813b4b Improve switch_version on Linux 2026-03-12 19:35:06 +05:30
Shivam Mathur
a33066c001 Merge pull request #1065 from shivammathur/dependabot/github_actions/develop/actions/download-artifact-8
Bump actions/download-artifact from 7 to 8
2026-03-09 17:10:54 +05:30
Shivam Mathur
f1643fd598 Merge pull request #1066 from shivammathur/dependabot/github_actions/develop/actions/upload-artifact-7
Bump actions/upload-artifact from 6 to 7
2026-03-09 17:10:45 +05:30
dependabot[bot]
e167f5c259 Bump actions/upload-artifact from 6 to 7
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 6 to 7.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v6...v7)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-09 11:18:10 +00:00
dependabot[bot]
45158d762a Bump actions/download-artifact from 7 to 8
Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 7 to 8.
- [Release notes](https://github.com/actions/download-artifact/releases)
- [Commits](https://github.com/actions/download-artifact/compare/v7...v8)

---
updated-dependencies:
- dependency-name: actions/download-artifact
  dependency-version: '8'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-09 11:18:07 +00:00
Shivam Mathur
8724c1dcc1 Add macos-26-intel to the README [skip ci] 2026-03-09 01:03:00 +05:30
Shivam Mathur
a1e0f566a8 Revert to old brew linking behaviour 2026-03-08 17:23:40 +05:30
Shivam Mathur
650d05dc41 Merge pull request #1064 from theluckystrike/main
fix: use latest in URL when version is empty
2026-03-04 21:05:48 +05:30
theluckystrike
746e1a46d1 fix: use latest in URL when version is empty
When version is not specified (empty string), the getUrl function
was generating invalid URLs like /releases/download/cs2pr without
any version, which returned 404.

This fix ensures that when version is empty or 'latest', the URL
includes '/latest/' which GitHub handles correctly with a redirect
to the actual latest release.

Fixes #1063
2026-03-04 22:03:38 +07:00
Shivam Mathur
a8ca9e3783 Fix npm audit 2026-02-28 11:55:28 +05:30
Shivam Mathur
769a4a81fd Update sqlsrv and pdo_sqlsrv versions [skip ci] 2026-02-28 02:22:42 +05:30
Shivam Mathur
d042aafd13 Add retry and timeout for stuck brew calls 2026-02-23 15:42:41 +05:30
Shivam Mathur
f97ca00780 Bump dependencies 2026-02-23 15:42:41 +05:30
Shivam Mathur
3aeeb03660 Update PHP version and OS support in README [skip ci] 2026-02-17 03:59:36 +05:30
Shivam Mathur
57e8183dae Switch to brew formula for pdo_firebird on macos [skip ci] 2026-02-08 11:29:04 +05:30
Shivam Mathur
5e98c022f7 Update dependencies 2026-02-08 09:13:03 +05:30
Shivam Mathur
f0b3fd9afe Update brew extensions 2026-02-08 07:56:22 +05:30
Shivam Mathur
1eee54fe48 Fix paths for bin tools and scoped tools [skip ci] 2026-01-23 01:04:38 +05:30
Shivam Mathur
341bc9e176 Improve cache for tools 2026-01-21 17:02:28 +05:30
Shivam Mathur
185f9de395 Fix linking tools 2026-01-21 15:54:25 +05:30
Shivam Mathur
46cb5030ab Replace generic record interfaces with specific ones 2026-01-21 02:46:20 +05:30
Shivam Mathur
6300a313a9 Remove unused @actions/io 2026-01-20 08:15:36 +05:30
Shivam Mathur
b7741bd785 Fix eslint config for imports 2026-01-20 07:47:37 +05:30
Shivam Mathur
109ae4d1c0 Reduce bundle size
Replace @actions/core with local functions

@actions/core is unmaintained and poorly designed for projects that just need basic functions
2026-01-20 07:47:23 +05:30
Shivam Mathur
871ff01b2b Refactor to use ES2024+ features for Node 24
Use Set for O(1) redirect status code lookup in fetch.ts

Use at(-1) and Object.hasOwn() in tools.ts

Use for...of with entries() in utils.ts
2026-01-20 06:46:28 +05:30
Shivam Mathur
f0e37f9e90 Update Node version in node workflow 2026-01-20 06:30:53 +05:30
Shivam Mathur
46ae35f333 Update to Node 24
Update fetch.ts to use native fetch() API

Use immutable sort in tools.ts
2026-01-20 06:29:00 +05:30
Shivam Mathur
f89a301251 Override undici version 2026-01-17 03:15:23 +05:30
Shivam Mathur
5efa2a774e Fix support to parse master in php-version input 2025-12-18 21:36:10 +05:30
Shivam Mathur
f72fc99524 Merge pull request #1045 from shivammathur/dependabot/github_actions/develop/actions/download-artifact-7
Bump actions/download-artifact from 6 to 7
2025-12-15 15:54:23 +05:30
Shivam Mathur
c84edb415f Merge pull request #1047 from shivammathur/dependabot/github_actions/develop/actions/upload-artifact-6
Bump actions/upload-artifact from 5 to 6
2025-12-15 15:54:03 +05:30
Shivam Mathur
4dda6da925 Merge pull request #1046 from shivammathur/dependabot/github_actions/develop/actions/cache-5
Bump actions/cache from 4 to 5
2025-12-15 15:53:27 +05:30
dependabot[bot]
c14319add5 Bump actions/upload-artifact from 5 to 6
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 5 to 6.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v5...v6)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-15 10:02:24 +00:00
dependabot[bot]
5ba12107fc Bump actions/cache from 4 to 5
Bumps [actions/cache](https://github.com/actions/cache) from 4 to 5.
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](https://github.com/actions/cache/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/cache
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-15 10:02:21 +00:00
dependabot[bot]
8bd624e171 Bump actions/download-artifact from 6 to 7
Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 6 to 7.
- [Release notes](https://github.com/actions/download-artifact/releases)
- [Commits](https://github.com/actions/download-artifact/compare/v6...v7)

---
updated-dependencies:
- dependency-name: actions/download-artifact
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-15 10:02:16 +00:00
Shivam Mathur
3dfaca4ee1 Rename nightly_versions -> php_builder_versions 2025-11-26 22:22:06 +05:30
75 changed files with 3107 additions and 4455 deletions

View File

@@ -88,7 +88,7 @@ jobs:
Remove-Item "$env:file.all" -Force Remove-Item "$env:file.all" -Force
Remove-Item "$env:file.builtin" -Force Remove-Item "$env:file.builtin" -Force
- name: Upload artifacts - name: Upload artifacts
uses: actions/upload-artifact@v5 uses: actions/upload-artifact@v7
with: with:
name: lists-php${{ matrix.php-versions }}-${{ matrix.operating-system }}.md name: lists-php${{ matrix.php-versions }}-${{ matrix.operating-system }}.md
path: php${{ matrix.php-versions }}-${{ matrix.operating-system }}.md path: php${{ matrix.php-versions }}-${{ matrix.operating-system }}.md
@@ -105,7 +105,7 @@ jobs:
with: with:
repository: ${{ github.repository }}.wiki repository: ${{ github.repository }}.wiki
- name: Download artifacts - name: Download artifacts
uses: actions/download-artifact@v6 uses: actions/download-artifact@v8
with: with:
path: ${{ github.workspace }}/lists path: ${{ github.workspace }}/lists
pattern: lists-* pattern: lists-*

View File

@@ -33,10 +33,10 @@ jobs:
with: with:
fetch-depth: 2 fetch-depth: 2
- name: Setup Node.js 20.x - name: Setup Node.js 24.x
uses: actions/setup-node@v6 uses: actions/setup-node@v6
with: with:
node-version: 20.x node-version: 24.x
- name: Install dependencies - name: Install dependencies
run: npm install run: npm install
@@ -54,7 +54,7 @@ jobs:
run: npm audit run: npm audit
- name: Send Coverage - name: Send Coverage
uses: codecov/codecov-action@v5 uses: codecov/codecov-action@v6
with: with:
token: ${{ secrets.CODECOV_TOKEN }} token: ${{ secrets.CODECOV_TOKEN }}
files: coverage/lcov.info files: coverage/lcov.info

View File

@@ -50,7 +50,7 @@ jobs:
key: ${{ env.key }} key: ${{ env.key }}
- name: Cache extensions - name: Cache extensions
uses: actions/cache@v4 uses: actions/cache@v5
with: with:
path: ${{ steps.cache-env.outputs.dir }} path: ${{ steps.cache-env.outputs.dir }}
key: ${{ steps.cache-env.outputs.key }} key: ${{ steps.cache-env.outputs.key }}

View File

@@ -16,6 +16,7 @@ jobs:
permissions: permissions:
contents: read contents: read
packages: write packages: write
id-token: write
steps: steps:
- name: Checkout release - name: Checkout release
if: github.event_name != 'workflow_dispatch' if: github.event_name != 'workflow_dispatch'
@@ -30,7 +31,7 @@ jobs:
- name: Setup Node.js - name: Setup Node.js
uses: actions/setup-node@v6 uses: actions/setup-node@v6
with: with:
node-version: '20.x' node-version: '24.x'
registry-url: https://registry.npmjs.org registry-url: https://registry.npmjs.org
- name: Install dependencies and add lib - name: Install dependencies and add lib
@@ -42,8 +43,6 @@ jobs:
- name: Publish to NPM - name: Publish to NPM
if: "!contains(github.event.inputs.skip, 'skip-npm')" if: "!contains(github.event.inputs.skip, 'skip-npm')"
run: npm publish --access public run: npm publish --access public
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
- name: Change to GitHub Packages registry - name: Change to GitHub Packages registry
uses: actions/setup-node@v6 uses: actions/setup-node@v6

View File

@@ -74,13 +74,13 @@ Both `GitHub-hosted` and `self-hosted` runners are supported by `setup-php` on t
| Ubuntu 22.04 | x86_64 | `ubuntu-22.04` | `PHP 8.1` | | Ubuntu 22.04 | x86_64 | `ubuntu-22.04` | `PHP 8.1` |
| Ubuntu 24.04 | aarch64 | `ubuntu-24.04-arm` | `PHP 8.3` | | Ubuntu 24.04 | aarch64 | `ubuntu-24.04-arm` | `PHP 8.3` |
| Ubuntu 22.04 | aarch64 | `ubuntu-22.04-arm` | `PHP 8.1` | | Ubuntu 22.04 | aarch64 | `ubuntu-22.04-arm` | `PHP 8.1` |
| Windows Server 2025 | x64 | `windows-2025` | `PHP 8.3` | | Windows Server 2025 | x64 | `windows-2025` | `PHP 8.5` |
| Windows Server 2022 | x64 | `windows-latest` or `windows-2022` | `PHP 8.3` | | Windows Server 2022 | x64 | `windows-latest` or `windows-2022` | `PHP 8.5` |
| macOS Tahoe 26.x | arm64 | `macos-26` | - | | macOS Tahoe 26.x | arm64 | `macos-26` | - |
| macOS Sequoia 15.x | arm64 | `macos-latest` or `macos-15` | - | | macOS Sequoia 15.x | arm64 | `macos-latest` or `macos-15` | - |
| macOS Sonoma 14.x | arm64 | `macos-14` | - | | macOS Sonoma 14.x | arm64 | `macos-14` | - |
| macOS Sequoia 15.x | x86_64 | `macos-15-intel` | `PHP 8.3` | | macOS Tahoe 26.x | x86_64 | `macos-26-intel` | `PHP 8.5` |
| macOS Ventura 13.x | x86_64 | `macos-13` | `PHP 8.3` | | macOS Sequoia 15.x | x86_64 | `macos-15-intel` | `PHP 8.5` |
### Self-Hosted Runners ### Self-Hosted Runners
@@ -121,9 +121,9 @@ On all supported OS/Platforms, the following PHP versions can be set up as per t
| `7.3` | `Stable` | `End of life` | `GitHub-hosted`, `self-hosted` | | `7.3` | `Stable` | `End of life` | `GitHub-hosted`, `self-hosted` |
| `7.4` | `Stable` | `End of life` | `GitHub-hosted`, `self-hosted` | | `7.4` | `Stable` | `End of life` | `GitHub-hosted`, `self-hosted` |
| `8.0` | `Stable` | `End of life` | `GitHub-hosted`, `self-hosted` | | `8.0` | `Stable` | `End of life` | `GitHub-hosted`, `self-hosted` |
| `8.1` | `Stable` | `Security fixes only` | `GitHub-hosted`, `self-hosted` | | `8.1` | `Stable` | `End of life` | `GitHub-hosted`, `self-hosted` |
| `8.2` | `Stable` | `Security fixes only` | `GitHub-hosted`, `self-hosted` | | `8.2` | `Stable` | `Security fixes only` | `GitHub-hosted`, `self-hosted` |
| `8.3` | `Stable` | `Active` | `GitHub-hosted`, `self-hosted` | | `8.3` | `Stable` | `Security fixes only` | `GitHub-hosted`, `self-hosted` |
| `8.4` | `Stable` | `Active` | `GitHub-hosted`, `self-hosted` | | `8.4` | `Stable` | `Active` | `GitHub-hosted`, `self-hosted` |
| `8.5` | `Stable` | `Active` | `GitHub-hosted`, `self-hosted` | | `8.5` | `Stable` | `Active` | `GitHub-hosted`, `self-hosted` |
| `8.6` | `Nightly` | `In development` | `GitHub-hosted`, `self-hosted` | | `8.6` | `Nightly` | `In development` | `GitHub-hosted`, `self-hosted` |
@@ -268,7 +268,7 @@ These tools can be set up globally using the `tools` input. It accepts a string
uses: shivammathur/setup-php@v2 uses: shivammathur/setup-php@v2
with: with:
php-version: '8.5' php-version: '8.5'
tools: composer:v1 tools: composer:v2
``` ```
- If you do not use composer in your workflow, you can specify `tools: none` to skip it. - If you do not use composer in your workflow, you can specify `tools: none` to skip it.
@@ -950,11 +950,12 @@ Examples of using `setup-php` with various PHP frameworks and packages.
| Framework/Package | Runs on | Workflow | | Framework/Package | Runs on | Workflow |
|----------------------------------------|---------------------------------|---------------------------------------------------------------------------------------------------------------| |----------------------------------------|---------------------------------|---------------------------------------------------------------------------------------------------------------|
| Blackfire | `macOS`, `ubuntu` and `windows` | [blackfire.yml](./examples/blackfire.yml "GitHub Action using Blackfire") | | Blackfire | `macOS`, `ubuntu` and `windows` | [blackfire.yml](./examples/blackfire.yml "GitHub Action using Blackfire") |
| Blackfire Player | `macOS`, `ubuntu` and `windows` | [blackfire-player.yml](./examples/blackfire-player.yml "GitHub Action using Blackfire Player") | | Blackfire Player | `macOS` and `ubuntu` | [blackfire-player.yml](./examples/blackfire-player.yml "GitHub Action using Blackfire Player") |
| CakePHP with `MySQL` and `Redis` | `ubuntu` | [cakephp-mysql.yml](./examples/cakephp-mysql.yml "GitHub Action for CakePHP with MySQL and Redis") | | CakePHP with `MySQL` and `Redis` | `ubuntu` | [cakephp-mysql.yml](./examples/cakephp-mysql.yml "GitHub Action for CakePHP with MySQL and Redis") |
| CakePHP with `PostgreSQL` and `Redis` | `ubuntu` | [cakephp-postgres.yml](./examples/cakephp-postgres.yml "GitHub Action for CakePHP with Postgres and Redis") | | CakePHP with `PostgreSQL` and `Redis` | `ubuntu` | [cakephp-postgres.yml](./examples/cakephp-postgres.yml "GitHub Action for CakePHP with Postgres and Redis") |
| CakePHP without services | `macOS`, `ubuntu` and `windows` | [cakephp.yml](./examples/cakephp.yml "GitHub Action for CakePHP without services") | | CakePHP without services | `macOS`, `ubuntu` and `windows` | [cakephp.yml](./examples/cakephp.yml "GitHub Action for CakePHP without services") |
| CodeIgniter | `macOS`, `ubuntu` and `windows` | [codeigniter.yml](./examples/codeigniter.yml "GitHub Action for CodeIgniter") | | CodeIgniter | `macOS`, `ubuntu` and `windows` | [codeigniter.yml](./examples/codeigniter.yml "GitHub Action for CodeIgniter") |
| Drupal 11 (Composer-managed) | `ubuntu` | [drupal.yml](./examples/drupal.yml "GitHub Action for Drupal 11 composer-managed projects") |
| Laminas MVC | `macOS`, `ubuntu` and `windows` | [laminas-mvc.yml](./examples/laminas-mvc.yml "GitHub Action for Laminas Framework MVC Projects") | | Laminas MVC | `macOS`, `ubuntu` and `windows` | [laminas-mvc.yml](./examples/laminas-mvc.yml "GitHub Action for Laminas Framework MVC Projects") |
| Laravel with `MySQL` and `Redis` | `ubuntu` | [laravel-mysql.yml](./examples/laravel-mysql.yml "GitHub Action for Laravel with MySQL and Redis") | | Laravel with `MySQL` and `Redis` | `ubuntu` | [laravel-mysql.yml](./examples/laravel-mysql.yml "GitHub Action for Laravel with MySQL and Redis") |
| Laravel with `PostgreSQL` and `Redis` | `ubuntu` | [laravel-postgres.yml](./examples/laravel-postgres.yml "GitHub Action for Laravel with PostgreSQL and Redis") | | Laravel with `PostgreSQL` and `Redis` | `ubuntu` | [laravel-postgres.yml](./examples/laravel-postgres.yml "GitHub Action for Laravel with PostgreSQL and Redis") |
@@ -964,14 +965,16 @@ Examples of using `setup-php` with various PHP frameworks and packages.
| Lumen without services | `macOS`, `ubuntu` and `windows` | [lumen.yml](./examples/lumen.yml "GitHub Action for Lumen without services") | | Lumen without services | `macOS`, `ubuntu` and `windows` | [lumen.yml](./examples/lumen.yml "GitHub Action for Lumen without services") |
| Phalcon with `MySQL` | `ubuntu` | [phalcon-mysql.yml](./examples/phalcon-mysql.yml "GitHub Action for Phalcon with MySQL") | | Phalcon with `MySQL` | `ubuntu` | [phalcon-mysql.yml](./examples/phalcon-mysql.yml "GitHub Action for Phalcon with MySQL") |
| Phalcon with `PostgreSQL` | `ubuntu` | [phalcon-postgres.yml](./examples/phalcon-postgres.yml "GitHub Action for Phalcon with PostgreSQL") | | Phalcon with `PostgreSQL` | `ubuntu` | [phalcon-postgres.yml](./examples/phalcon-postgres.yml "GitHub Action for Phalcon with PostgreSQL") |
| Roots/bedrock | `ubuntu` | [bedrock.yml](./examples/bedrock.yml "GitHub Action for Wordpress Development using @roots/bedrock") |
| Roots/sage | `ubuntu` | [sage.yml](./examples/sage.yml "GitHub Action for Wordpress Development using @roots/sage") |
| Slim Framework | `macOS`, `ubuntu` and `windows` | [slim-framework.yml](./examples/slim-framework.yml "GitHub Action for Slim Framework") | | Slim Framework | `macOS`, `ubuntu` and `windows` | [slim-framework.yml](./examples/slim-framework.yml "GitHub Action for Slim Framework") |
| Symfony with `MySQL` | `ubuntu` | [symfony-mysql.yml](./examples/symfony-mysql.yml "GitHub Action for Symfony with MySQL") | | Symfony with `MySQL` | `ubuntu` | [symfony-mysql.yml](./examples/symfony-mysql.yml "GitHub Action for Symfony with MySQL") |
| Symfony with `PostgreSQL` | `ubuntu` | [symfony-postgres.yml](./examples/symfony-postgres.yml "GitHub Action for Symfony with PostgreSQL") | | Symfony with `PostgreSQL` | `ubuntu` | [symfony-postgres.yml](./examples/symfony-postgres.yml "GitHub Action for Symfony with PostgreSQL") |
| Symfony without services | `macOS`, `ubuntu` and `windows` | [symfony.yml](./examples/symfony.yml "GitHub Action for Symfony without services") | | Symfony without services | `macOS`, `ubuntu` and `windows` | [symfony.yml](./examples/symfony.yml "GitHub Action for Symfony without services") |
| Yii2 Starter Kit with `MySQL` | `ubuntu` | [yii2-mysql.yml](./examples/yii2-mysql.yml "GitHub Action for Yii2 Starter Kit with MySQL") | | WordPress plugin | `ubuntu` | [wordpress.yml](./examples/wordpress.yml "GitHub Action for WordPress plugins") |
| Yii2 Starter Kit with `PostgreSQL` | `ubuntu` | [yii2-postgres.yml](./examples/yii2-postgres.yml "GitHub Action for Yii2 Starter Kit with PostgreSQL") | | WordPress with Roots/Bedrock | `ubuntu` | [bedrock.yml](./examples/bedrock.yml "GitHub Action for WordPress development using @roots/bedrock") |
| WordPress with Roots/Sage | `ubuntu` | [sage.yml](./examples/sage.yml "GitHub Action for WordPress development using @roots/sage") |
| Yii3 web application with `MySQL` | `ubuntu` | [yii3-mysql.yml](./examples/yii3-mysql.yml "GitHub Action for Yii3 web application with MySQL") |
| Yii3 web application with `PostgreSQL` | `ubuntu` | [yii3-postgres.yml](./examples/yii3-postgres.yml "GitHub Action for Yii3 web application with PostgreSQL") |
| Yii3 web application | `ubuntu` and `windows` | [yii3.yml](./examples/yii3.yml "GitHub Action for Yii3 web application") |
## :bookmark: Versioning ## :bookmark: Versioning

View File

@@ -2,14 +2,18 @@ import * as config from '../src/config';
describe('Config tests', () => { describe('Config tests', () => {
it.each` it.each`
ini_values | os | output ini_values | os | output
${'a=b, c=d'} | ${'win32'} | ${'Add-Content "$php_dir\\php.ini" "a=b\nc=d"'} ${'a=b, c=d'} | ${'win32'} | ${'Add-Content "$php_dir\\php.ini" "a=b\nc=d"'}
${'a=b, c=d'} | ${'linux'} | ${'echo "a=b\nc=d" | sudo tee -a "${pecl_file:-${ini_file[@]}}"'} ${'a=b, c=d'} | ${'linux'} | ${'echo "a=b\nc=d" | sudo tee -a "${pecl_file:-${ini_file[@]}}"'}
${'a=b, c=d'} | ${'darwin'} | ${'echo "a=b\nc=d" | sudo tee -a "${pecl_file:-${ini_file[@]}}"'} ${'a=b, c=d'} | ${'darwin'} | ${'echo "a=b\nc=d" | sudo tee -a "${pecl_file:-${ini_file[@]}}"'}
${'a=b & ~c'} | ${'win32'} | ${'Add-Content "$php_dir\\php.ini" "a=\'b & ~c\'"'} ${'a=b & ~c'} | ${'win32'} | ${'Add-Content "$php_dir\\php.ini" "a=\'b & ~c\'"'}
${'a="~(b)"'} | ${'win32'} | ${'Add-Content "$php_dir\\php.ini" "a=\'~(b)\'"'} ${'a="~(b)"'} | ${'win32'} | ${'Add-Content "$php_dir\\php.ini" "a=\'~(b)\'"'}
${'a="b, c"'} | ${'win32'} | ${'Add-Content "$php_dir\\php.ini" "a=b, c"'} ${'a="b, c"'} | ${'win32'} | ${'Add-Content "$php_dir\\php.ini" "a=b, c"'}
${'a=b, c=d'} | ${'openbsd'} | ${'Platform openbsd is not supported'} ${'disable_functions="exec,system"'} | ${'linux'} | ${'echo "disable_functions=exec,system" | sudo tee -a'}
${'disable_functions="exec,system"'} | ${'win32'} | ${'Add-Content "$php_dir\\php.ini" "disable_functions=exec,system"'}
${'a=$(id)'} | ${'linux'} | ${'echo "a=\'\\$(id)\'"'}
${'a=$(id)'} | ${'win32'} | ${'Add-Content "$php_dir\\php.ini" "a=\'`$(id)\'"'}
${'a=b, c=d'} | ${'openbsd'} | ${'Platform openbsd is not supported'}
`('checking addINIValues on $os', async ({ini_values, os, output}) => { `('checking addINIValues on $os', async ({ini_values, os, output}) => {
expect(await config.addINIValues(ini_values, os)).toContain(output); expect(await config.addINIValues(ini_values, os)).toContain(output);
}); });

106
__tests__/core.test.ts Normal file
View File

@@ -0,0 +1,106 @@
import * as core from '../src/core';
describe('Core tests', () => {
const originalEnv = process.env;
const originalExitCode = process.exitCode;
let stdoutOutput: string;
const originalWrite = process.stdout.write;
beforeEach(() => {
process.env = {...originalEnv};
process.exitCode = undefined;
stdoutOutput = '';
process.stdout.write = jest.fn((chunk: string | Uint8Array): boolean => {
stdoutOutput += chunk.toString();
return true;
}) as unknown as typeof process.stdout.write;
});
afterEach(() => {
process.env = originalEnv;
process.exitCode = originalExitCode;
process.stdout.write = originalWrite;
});
it('checking issueCommand with no properties', () => {
core.issueCommand('warning', {}, 'test message');
expect(stdoutOutput).toContain('::warning::test message');
});
it('checking issueCommand with properties', () => {
core.issueCommand('error', {file: 'test.ts', line: '10'}, 'error message');
expect(stdoutOutput).toContain(
'::error file=test.ts,line=10::error message'
);
});
it('checking issueCommand escapes special characters in message', () => {
core.issueCommand('warning', {}, 'line1\nline2\rline3%percent');
expect(stdoutOutput).toContain(
'::warning::line1%0Aline2%0Dline3%25percent'
);
});
it('checking issueCommand escapes special characters in properties', () => {
core.issueCommand('error', {file: 'path:to,file'}, 'message');
expect(stdoutOutput).toContain('::error file=path%3Ato%2Cfile::message');
});
it('checking issueCommand with Error object', () => {
const error = new Error('test error');
core.issueCommand('error', {}, error);
expect(stdoutOutput).toContain('::error::Error: test error');
});
it('checking issueCommand filters empty properties', () => {
core.issueCommand('warning', {file: 'test.ts', line: ''}, 'message');
expect(stdoutOutput).toContain('::warning file=test.ts::message');
});
it('checking error', () => {
core.error('error message');
expect(stdoutOutput).toContain('::error::error message');
});
it('checking error with Error object', () => {
core.error(new Error('error instance'));
expect(stdoutOutput).toContain('::error::Error: error instance');
});
it('checking setFailed', () => {
core.setFailed('failure message');
expect(process.exitCode).toBe(1);
expect(stdoutOutput).toContain('::error::failure message');
});
it('checking setFailed with Error object', () => {
core.setFailed(new Error('failure error'));
expect(process.exitCode).toBe(1);
expect(stdoutOutput).toContain('::error::Error: failure error');
});
it('checking getInput returns value', () => {
process.env['INPUT_TEST-INPUT'] = 'test value';
expect(core.getInput('test-input')).toBe('test value');
});
it('checking getInput trims value', () => {
process.env['INPUT_TEST-INPUT'] = ' trimmed ';
expect(core.getInput('test-input')).toBe('trimmed');
});
it('checking getInput returns empty string for missing input', () => {
expect(core.getInput('missing-input')).toBe('');
});
it('checking getInput throws for required missing input', () => {
expect(() => core.getInput('missing-input', true)).toThrow(
'Input required and not supplied: missing-input'
);
});
it('checking getInput handles spaces in name', () => {
process.env['INPUT_INPUT_WITH_SPACES'] = 'spaced value';
expect(core.getInput('input with spaces')).toBe('spaced value');
});
});

View File

@@ -21,6 +21,7 @@ describe('Extension tests', () => {
${'ibm_db2'} | ${'7.4'} | ${'Add-Ibm ibm_db2'} ${'ibm_db2'} | ${'7.4'} | ${'Add-Ibm ibm_db2'}
${'pdo_ibm'} | ${'7.4'} | ${'Add-Ibm pdo_ibm'} ${'pdo_ibm'} | ${'7.4'} | ${'Add-Ibm pdo_ibm'}
${'pecl_http'} | ${'7.4'} | ${'Add-Http'} ${'pecl_http'} | ${'7.4'} | ${'Add-Http'}
${'http'} | ${'8.5'} | ${'Add-Http'}
${'pdo_sqlsrv'} | ${'7.4'} | ${'Add-Sqlsrv pdo_sqlsrv'} ${'pdo_sqlsrv'} | ${'7.4'} | ${'Add-Sqlsrv pdo_sqlsrv'}
${'phalcon3'} | ${'7.2'} | ${'Add-Phalcon phalcon3'} ${'phalcon3'} | ${'7.2'} | ${'Add-Phalcon phalcon3'}
${'phalcon4'} | ${'7.4'} | ${'Add-Phalcon phalcon4'} ${'phalcon4'} | ${'7.4'} | ${'Add-Phalcon phalcon4'}
@@ -131,7 +132,11 @@ describe('Extension tests', () => {
) )
? `add_${ext_name}` ? `add_${ext_name}`
: `add_brew_extension ${formula} ${prefix}`; : `add_brew_extension ${formula} ${prefix}`;
return [formula, formula === 'phalcon3' ? '7.3' : '7.4', output]; return [
formula,
formula.match(/phalcon3|lua|propro/) ? '7.3' : '8.1',
output
];
}); });
it.each(data)( it.each(data)(

View File

@@ -19,7 +19,9 @@ it('checking fetch', async () => {
Location: host_url + '/pong' Location: host_url + '/pong'
}) })
.get('/pong') .get('/pong')
.reply(200, 'pong'); .reply(200, 'pong')
.get('/error')
.replyWithError('Network failure');
let response: Record<string, string> = await fetch.fetch(manifest_url); let response: Record<string, string> = await fetch.fetch(manifest_url);
expect(response.error).toBe(undefined); expect(response.error).toBe(undefined);
@@ -36,4 +38,8 @@ it('checking fetch', async () => {
response = await fetch.fetch(manifest_url, 'invalid_token'); response = await fetch.fetch(manifest_url, 'invalid_token');
expect(response.error).not.toBe(undefined); expect(response.error).not.toBe(undefined);
expect(response.data).toBe(undefined); expect(response.data).toBe(undefined);
response = await fetch.fetch(host_url + '/error');
expect(response.error).toContain('Fetch error:');
expect(response.data).toBe(undefined);
}); });

View File

@@ -1,43 +1,42 @@
import * as fs from 'fs'; import * as fs from 'fs';
import * as tools from '../src/tools'; import * as tools from '../src/tools';
import {ToolData, ToolInput} from '../src/tools';
interface IData { function getData(data: Partial<ToolData>): ToolData {
tool: string; const tool = data.tool || 'tool';
version?: string; const version = data.version || '';
domain?: string;
extension?: string;
os?: string;
php_version?: string;
release?: string;
repository?: string;
scope?: string;
type?: string;
fetch_latest?: string;
version_parameter?: string;
version_prefix?: string;
}
function getData(data: IData): Record<string, string> {
return { return {
tool: data.tool, tool,
version: data.version || '', version,
url: data.url || '',
domain: data.domain || 'https://example.com', domain: data.domain || 'https://example.com',
extension: data.extension || '.phar', extension: data.extension || '.phar',
os: data.os || 'linux', os: data.os || 'linux',
php_version: data.php_version || '7.4', php_version: data.php_version || '7.4',
release: data.release || [data.tool, data.version].join(':'), release: data.release || [tool, version].join(':'),
repository: data.repository || '', repository: data.repository || '',
scope: data.scope || 'global', scope: data.scope || 'global',
type: data.type || 'phar', type: data.type || 'phar',
fetch_latest: data.fetch_latest || 'false', fetch_latest: data.fetch_latest || 'false',
version_parameter: data.version_parameter || '-V', version_parameter: data.version_parameter || '-V',
version_prefix: data.version_prefix || '', version_prefix: data.version_prefix || '',
github: 'https://github.com', github: data.github || 'https://github.com',
prefix: 'releases', prefix: data.prefix || 'releases',
verb: 'download' verb: data.verb || 'download',
packagist: data.packagist || data.repository || '',
function: data.function,
alias: data.alias,
uri: data.uri,
error: data.error
}; };
} }
function unsetComposerAuthEnv(): void {
delete process.env['GITHUB_TOKEN'];
delete process.env['COMPOSER_TOKEN'];
delete process.env['COMPOSER_AUTH_JSON'];
}
/** /**
* Mock fetch.ts * Mock fetch.ts
*/ */
@@ -161,6 +160,18 @@ describe('Tools tests', () => {
} }
); );
it('checking getLatestVersion with fetch_latest=true but no repository', async () => {
expect(
await tools.getLatestVersion(
getData({
tool: 'tool',
repository: '',
fetch_latest: 'true'
})
)
).toBe('latest');
});
it.each` it.each`
version | tool | type | expected version | tool | type | expected
${'latest'} | ${'tool'} | ${'phar'} | ${'latest'} ${'latest'} | ${'tool'} | ${'phar'} | ${'latest'}
@@ -176,6 +187,7 @@ describe('Tools tests', () => {
${'1.2.3-dev'} | ${'tool'} | ${'phar'} | ${'1.2.3-dev'} ${'1.2.3-dev'} | ${'tool'} | ${'phar'} | ${'1.2.3-dev'}
${'1.2.3-alpha1'} | ${'tool'} | ${'phar'} | ${'1.2.3-alpha1'} ${'1.2.3-alpha1'} | ${'tool'} | ${'phar'} | ${'1.2.3-alpha1'}
${'1.2.3-alpha.1'} | ${'tool'} | ${'phar'} | ${'1.2.3-alpha.1'} ${'1.2.3-alpha.1'} | ${'tool'} | ${'phar'} | ${'1.2.3-alpha.1'}
${'1.>=0'} | ${'tool'} | ${'phar'} | ${'1.0'}
`( `(
'checking getVersion: $version, $tool, $type', 'checking getVersion: $version, $tool, $type',
async ({version, tool, type, expected}) => { async ({version, tool, type, expected}) => {
@@ -245,15 +257,17 @@ describe('Tools tests', () => {
); );
it('checking getUrl handles undefined version without double slash', async () => { it('checking getUrl handles undefined version without double slash', async () => {
const data = getData({ const data: ToolInput = {
tool: 'cs2pr', ...getData({
repository: 'staabm/annotate-pull-request-from-checkstyle', tool: 'cs2pr',
domain: 'https://github.com' repository: 'staabm/annotate-pull-request-from-checkstyle',
}); domain: 'https://github.com'
data['extension'] = ''; }),
delete data['version']; version: undefined
};
data.extension = '';
expect(await tools.getUrl(data)).toBe( expect(await tools.getUrl(data)).toBe(
'https://github.com/staabm/annotate-pull-request-from-checkstyle/releases/download/cs2pr' 'https://github.com/staabm/annotate-pull-request-from-checkstyle/releases/latest/download/cs2pr'
); );
}); });
@@ -284,29 +298,37 @@ describe('Tools tests', () => {
tool: 'tool', tool: 'tool',
version: 'latest', version: 'latest',
version_parameter: JSON.stringify('-v'), version_parameter: JSON.stringify('-v'),
os: os os: os,
url: 'https://example.com/tool.phar'
}); });
data['url'] = 'https://example.com/tool.phar';
expect(await tools.addArchive(data)).toContain(script); expect(await tools.addArchive(data)).toContain(script);
}); });
it.each` it.each`
os | script | scope os | release | scope | script
${'linux'} | ${'add_composer_tool tool tool:1.2.3 user/ global'} | ${'global'} ${'linux'} | ${'tool:1.2.3'} | ${'global'} | ${'add_composer_tool tool tool:1.2.3 user/ global'}
${'darwin'} | ${'add_composer_tool tool tool:1.2.3 user/ scoped'} | ${'scoped'} ${'darwin'} | ${'tool:1.2.3'} | ${'scoped'} | ${'add_composer_tool tool tool:1.2.3 user/ scoped'}
${'win32'} | ${'Add-ComposerTool tool tool:1.2.3 user/ scoped'} | ${'scoped'} ${'win32'} | ${'tool:1.2.3'} | ${'scoped'} | ${'Add-ComposerTool tool tool:1.2.3 user/ scoped'}
${'openbsd'} | ${'Platform openbsd is not supported'} | ${'global'} ${'linux'} | ${'tool:>=1.2'} | ${'global'} | ${'add_composer_tool tool "tool:>=1.2" user/ global'}
`('checking addPackage: $os, $scope', async ({os, script, scope}) => { ${'win32'} | ${'tool:>=1.2'} | ${'global'} | ${'Add-ComposerTool tool "tool:>=1.2" user/ global'}
const data = getData({ ${'linux'} | ${'tool:1.*'} | ${'global'} | ${'add_composer_tool tool "tool:1.*" user/ global'}
tool: 'tool', ${'linux'} | ${'psalm:^5||^6'} | ${'global'} | ${'add_composer_tool tool "psalm:^5||^6" user/ global'}
version: '1.2.3', ${'linux'} | ${'psalm:>=5,<6'} | ${'global'} | ${'add_composer_tool tool "psalm:>=5,<6" user/ global'}
repository: 'user/tool', ${'openbsd'} | ${'tool:1.2.3'} | ${'global'} | ${'Platform openbsd is not supported'}
os: os, `(
scope: scope 'checking addPackage: $os, $release',
}); async ({os, release, scope, script}) => {
data['release'] = [data['tool'], data['version']].join(':'); const data = getData({
expect(await tools.addPackage(data)).toContain(script); tool: 'tool',
}); version: '1.2.3',
repository: 'user/tool',
os,
scope
});
data['release'] = release;
expect(await tools.addPackage(data)).toContain(script);
}
);
it.each` it.each`
version | php_version | os | script version | php_version | os | script
@@ -416,6 +438,118 @@ describe('Tools tests', () => {
} }
); );
it.each`
version | affected
${'1'} | ${false}
${'1.0.0-alpha1'} | ${true}
${'1.0.0-alpha2'} | ${true}
${'1.0.0-alpha3'} | ${true}
${'1.0.0-alpha4'} | ${true}
${'1.0.0-alpha5'} | ${true}
${'1.0.0-alpha6'} | ${true}
${'1.0.0-alpha7'} | ${true}
${'1.0.0-alpha8'} | ${true}
${'1.0.0-alpha9'} | ${true}
${'1.0.0-alpha10'} | ${true}
${'1.0.0-alpha11'} | ${true}
${'1.0.0-beta1'} | ${true}
${'1.0.0-beta2'} | ${true}
${'1.0.0'} | ${true}
${'1.10.27'} | ${true}
${'1.10.28'} | ${false}
${'2.0.0-alpha1'} | ${true}
${'2.0.0-alpha2'} | ${true}
${'2.0.0-alpha3'} | ${true}
${'2.0.0-RC1'} | ${true}
${'2.0.0-RC2'} | ${true}
${'2.2.27'} | ${true}
${'2.2.28'} | ${false}
${'2.3.0-RC1'} | ${true}
${'2.3.0-RC2'} | ${true}
${'2.9.7'} | ${true}
${'2.9.7-RC1'} | ${true}
${'2.9.8'} | ${false}
${'2.9.0RC1'} | ${false}
${'2.9.x-dev'} | ${false}
`('checking affected composer version: $version', ({version, affected}) => {
expect(tools.skipGitHubAuthForComposerVersion(version)).toBe(affected);
});
it('checking affected composer version with CRLF ranges', async () => {
let affected = false;
let fixed = true;
await jest.isolateModulesAsync(async () => {
jest.doMock('fs', () => ({
...jest.requireActual('fs'),
readFileSync: (
filePath: fs.PathOrFileDescriptor,
options?: unknown
) => {
if (String(filePath).includes('composer-gh-auth-no-op')) {
return '1.0.0-0 1.10.28\r\n2.0.0-0 2.2.28\r\n2.3.0-0 2.9.8';
}
return (jest.requireActual('fs') as typeof fs).readFileSync(
filePath,
options as fs.ObjectEncodingOptions & {flag?: string}
);
}
}));
const isolatedTools = await import('../src/tools');
affected = isolatedTools.skipGitHubAuthForComposerVersion('2.9.7');
fixed = isolatedTools.skipGitHubAuthForComposerVersion('2.9.8');
});
expect(affected).toBe(true);
expect(fixed).toBe(false);
});
it.each`
auth_json | expected
${'{"github-oauth":{"github.com":"ghs_new-token"},"http-basic":{"repo.example":{"username":"u","password":"p"}}}'} | ${'{"http-basic":{"repo.example":{"username":"u","password":"p"}}}'}
${'{"github-oauth":{"github.com":"ghs_new-token"}}'} | ${undefined}
${'{"http-basic":{"repo.example":{"username":"u","password":"p"}}}'} | ${'{"http-basic":{"repo.example":{"username":"u","password":"p"}}}'}
${'{"nested":{"github-oauth":{"github.com":"ghs_new-token"}}}'} | ${'{"nested":{"github-oauth":{"github.com":"ghs_new-token"}}}'}
${'{"github-oauth":'} | ${'{"github-oauth":'}
`('cleaning composer auth json', ({auth_json, expected}) => {
unsetComposerAuthEnv();
process.env['COMPOSER_AUTH_JSON'] = auth_json;
tools.cleanComposerAuthJson();
expect(process.env['COMPOSER_AUTH_JSON']).toBe(expected);
unsetComposerAuthEnv();
});
it.each`
version | os | envs | skip_github_auth
${'latest'} | ${'linux'} | ${{GITHUB_TOKEN: 'ghs_token'}} | ${false}
${'1'} | ${'linux'} | ${{GITHUB_TOKEN: 'ghs_token'}} | ${false}
${'2'} | ${'linux'} | ${{GITHUB_TOKEN: 'ghs_token'}} | ${false}
${'2.9.7'} | ${'linux'} | ${{}} | ${true}
${'2.9.7'} | ${'linux'} | ${{GITHUB_TOKEN: 'ghs_token'}} | ${true}
${'2.9.7'} | ${'linux'} | ${{COMPOSER_TOKEN: 'ghs_token'}} | ${true}
${'2.9.7'} | ${'linux'} | ${{COMPOSER_AUTH_JSON: '{"github-oauth":{"github.com":"ghs_new-token"}}'}} | ${true}
${'2.9.7'} | ${'linux'} | ${{COMPOSER_AUTH_JSON: '{"http-basic":{"repo.example":{"username":"u","password":"p"}}}'}} | ${true}
${'2.9.8'} | ${'linux'} | ${{GITHUB_TOKEN: 'ghs_token'}} | ${false}
${'2.9.7'} | ${'win32'} | ${{GITHUB_TOKEN: 'ghs_token'}} | ${true}
`(
'checking composer github auth skip flag: $version, $os',
async ({version, os, envs, skip_github_auth}) => {
unsetComposerAuthEnv();
Object.assign(process.env, envs);
const data = getData({
tool: 'composer',
os: os,
php_version: '7.4',
domain: 'https://getcomposer.org',
repository: 'composer/composer',
version: version
});
const script = await tools.addComposer(data);
expect(script).toContain(
`composer ${version}${skip_github_auth ? ' true' : ''}`
);
unsetComposerAuthEnv();
}
);
it.each` it.each`
version | uri version | uri
${'latest'} | ${'wp-cli/builds/blob/gh-pages/phar/wp-cli.phar?raw=true'} ${'latest'} | ${'wp-cli/builds/blob/gh-pages/phar/wp-cli.phar?raw=true'}
@@ -526,7 +660,7 @@ describe('Tools tests', () => {
'add_devtools phpize', 'add_devtools phpize',
'add_tool https://github.com/phpmd/phpmd/releases/latest/download/phpmd.phar phpmd "--version"', 'add_tool https://github.com/phpmd/phpmd/releases/latest/download/phpmd.phar phpmd "--version"',
'add_tool https://github.com/phpspec/phpspec/releases/latest/download/phpspec.phar phpspec "-V"', 'add_tool https://github.com/phpspec/phpspec/releases/latest/download/phpspec.phar phpspec "-V"',
'add_composer_tool phpunit-bridge phpunit-bridge:5.6.* symfony/ global', 'add_composer_tool phpunit-bridge "phpunit-bridge:5.6.*" symfony/ global',
'add_composer_tool phpunit-polyfills phpunit-polyfills:1.0.1 yoast/ global', 'add_composer_tool phpunit-polyfills phpunit-polyfills:1.0.1 yoast/ global',
'add_protoc 1.2.3', 'add_protoc 1.2.3',
'add_tool https://github.com/vimeo/psalm/releases/latest/download/psalm.phar psalm "-v"', 'add_tool https://github.com/vimeo/psalm/releases/latest/download/psalm.phar psalm "-v"',
@@ -586,7 +720,7 @@ describe('Tools tests', () => {
'Add-ComposerTool codeception codeception codeception/ global', 'Add-ComposerTool codeception codeception codeception/ global',
'Add-ComposerTool prestissimo prestissimo hirak/ global', 'Add-ComposerTool prestissimo prestissimo hirak/ global',
'Add-ComposerTool automatic-composer-prefetcher automatic-composer-prefetcher narrowspark/ global', 'Add-ComposerTool automatic-composer-prefetcher automatic-composer-prefetcher narrowspark/ global',
'Add-ComposerTool phinx phinx:1.2.* robmorgan/ scoped', 'Add-ComposerTool phinx "phinx:1.2.*" robmorgan/ scoped',
'Add-ComposerTool phinx phinx:^1.2 robmorgan/ global', 'Add-ComposerTool phinx phinx:^1.2 robmorgan/ global',
'Add-ComposerTool tool tool:1.2.3 user/ global', 'Add-ComposerTool tool tool:1.2.3 user/ global',
'Add-ComposerTool tool tool:~1.2 user/ global' 'Add-ComposerTool tool tool:~1.2 user/ global'
@@ -635,6 +769,7 @@ describe('Tools tests', () => {
${'composer:preview'} | ${'add_tool https://github.com/shivammathur/composer-cache/releases/latest/download/composer-7.4-preview.phar,https://artifacts.setup-php.com/composer/composer-7.4-preview.phar,https://dl.cloudsmith.io/public/shivammathur/composer-cache/raw/files/composer-7.4-preview.phar,https://getcomposer.org/composer-preview.phar composer preview'} ${'composer:preview'} | ${'add_tool https://github.com/shivammathur/composer-cache/releases/latest/download/composer-7.4-preview.phar,https://artifacts.setup-php.com/composer/composer-7.4-preview.phar,https://dl.cloudsmith.io/public/shivammathur/composer-cache/raw/files/composer-7.4-preview.phar,https://getcomposer.org/composer-preview.phar composer preview'}
${'composer, composer:v1'} | ${'add_tool https://github.com/shivammathur/composer-cache/releases/latest/download/composer-7.4-1.phar,https://artifacts.setup-php.com/composer/composer-7.4-1.phar,https://dl.cloudsmith.io/public/shivammathur/composer-cache/raw/files/composer-7.4-1.phar,https://getcomposer.org/composer-1.phar composer'} ${'composer, composer:v1'} | ${'add_tool https://github.com/shivammathur/composer-cache/releases/latest/download/composer-7.4-1.phar,https://artifacts.setup-php.com/composer/composer-7.4-1.phar,https://dl.cloudsmith.io/public/shivammathur/composer-cache/raw/files/composer-7.4-1.phar,https://getcomposer.org/composer-1.phar composer'}
${'composer:v1, composer:preview, composer:snapshot'} | ${'add_tool https://github.com/shivammathur/composer-cache/releases/latest/download/composer-7.4-snapshot.phar,https://artifacts.setup-php.com/composer/composer-7.4-snapshot.phar,https://dl.cloudsmith.io/public/shivammathur/composer-cache/raw/files/composer-7.4-snapshot.phar,https://getcomposer.org/composer.phar composer snapshot'} ${'composer:v1, composer:preview, composer:snapshot'} | ${'add_tool https://github.com/shivammathur/composer-cache/releases/latest/download/composer-7.4-snapshot.phar,https://artifacts.setup-php.com/composer/composer-7.4-snapshot.phar,https://dl.cloudsmith.io/public/shivammathur/composer-cache/raw/files/composer-7.4-snapshot.phar,https://getcomposer.org/composer.phar composer snapshot'}
${'composer:2.9.7'} | ${'add_tool https://github.com/composer/composer/releases/download/2.9.7/composer.phar,https://getcomposer.org/download/2.9.7/composer.phar composer 2.9.7 true'}
`('checking composer setup: $tools_csv', async ({tools_csv, script}) => { `('checking composer setup: $tools_csv', async ({tools_csv, script}) => {
expect(await tools.addTools(tools_csv, '7.4', 'linux')).toContain(script); expect(await tools.addTools(tools_csv, '7.4', 'linux')).toContain(script);
}); });
@@ -649,14 +784,43 @@ describe('Tools tests', () => {
expect(await tools.addTools(tools_csv, '7.4', 'linux')).toContain(script); expect(await tools.addTools(tools_csv, '7.4', 'linux')).toContain(script);
}); });
it.each` it('checking error when custom-function tool is missing function field', async () => {
tools_csv | token | script const brokenToolsJson = JSON.stringify({
${'cs2pr:1.2'} | ${'invalid_token'} | ${'add_log "$cross" "cs2pr" "Invalid token"'} composer: {
${'phpunit:1.2'} | ${'invalid_token'} | ${'add_log "$cross" "phpunit" "Invalid token"'} type: 'custom-function',
${'phpunit:0.1'} | ${'no_data'} | ${'add_log "$cross" "phpunit" "No version found with prefix 0.1."'} domain: 'https://getcomposer.org',
`('checking error: $tools_csv', async ({tools_csv, token, script}) => { repository: 'composer/composer',
process.env['GITHUB_TOKEN'] = token; function: 'composer'
expect(await tools.addTools(tools_csv, '7.4', 'linux')).toContain(script); },
'broken-tool': {
type: 'custom-function'
}
});
let result: string = '';
await jest.isolateModulesAsync(async () => {
jest.doMock('fs', () => ({
...jest.requireActual('fs'),
readFileSync: (
filePath: fs.PathOrFileDescriptor,
options?: unknown
) => {
if (String(filePath).includes('tools.json')) {
return brokenToolsJson;
}
return (jest.requireActual('fs') as typeof fs).readFileSync(
filePath,
options as fs.ObjectEncodingOptions & {flag?: string}
);
}
}));
const isolatedTools = await import('../src/tools');
result = await isolatedTools.addTools('broken-tool', '7.4', 'linux');
});
expect(result).toContain(
'add_log "$cross" "broken-tool" "broken-tool has no function defined. Please report this issue."'
);
}); });
it.each` it.each`

View File

@@ -3,16 +3,6 @@ import * as path from 'path';
import * as utils from '../src/utils'; import * as utils from '../src/utils';
import * as fetchModule from '../src/fetch'; import * as fetchModule from '../src/fetch';
/**
* Mock @actions/core
*/
jest.mock('@actions/core', () => ({
getInput: jest.fn().mockImplementation(key => {
return ['setup-php'].indexOf(key) !== -1 ? key : '';
}),
info: jest.fn()
}));
describe('Utils tests', () => { describe('Utils tests', () => {
it('checking readEnv', async () => { it('checking readEnv', async () => {
process.env['test'] = 'setup-php'; process.env['test'] = 'setup-php';
@@ -26,12 +16,14 @@ describe('Utils tests', () => {
it('checking getInput', async () => { it('checking getInput', async () => {
process.env['test'] = 'setup-php'; process.env['test'] = 'setup-php';
process.env['INPUT_SETUP-PHP'] = 'setup-php';
expect(await utils.getInput('test', false)).toBe('setup-php'); expect(await utils.getInput('test', false)).toBe('setup-php');
expect(await utils.getInput('setup-php', false)).toBe('setup-php'); expect(await utils.getInput('setup-php', false)).toBe('setup-php');
expect(await utils.getInput('DoesNotExist', false)).toBe(''); expect(await utils.getInput('DoesNotExist', false)).toBe('');
await expect(async () => { await expect(async () => {
await utils.getInput('DoesNotExist', true); await utils.getInput('DoesNotExist', true);
}).rejects.toThrow('Input required and not supplied: DoesNotExist'); }).rejects.toThrow('Input required and not supplied: DoesNotExist');
delete process.env['INPUT_SETUP-PHP'];
}); });
it('checking getManifestURL', async () => { it('checking getManifestURL', async () => {
@@ -48,7 +40,19 @@ describe('Utils tests', () => {
expect(await utils.parseVersion('7')).toBe('7.0'); expect(await utils.parseVersion('7')).toBe('7.0');
expect(await utils.parseVersion('7.4')).toBe('7.4'); expect(await utils.parseVersion('7.4')).toBe('7.4');
expect(await utils.parseVersion('5.x')).toBe('5.6'); expect(await utils.parseVersion('5.x')).toBe('5.6');
expect(await utils.parseVersion('4.x')).toBe(undefined); expect(await utils.parseVersion('pre')).toBe('pre');
expect(await utils.parseVersion('pre-installed')).toBe('pre');
await expect(utils.parseVersion('4.x')).rejects.toThrow(
'Invalid PHP version: 4.x'
);
await expect(utils.parseVersion('foo')).rejects.toThrow(
'Invalid PHP version:'
);
fetchSpy.mockResolvedValue({data: '{ "latest": "8.1.0" }'});
await expect(utils.parseVersion('latest')).rejects.toThrow(
'Invalid PHP version in manifest:'
);
fetchSpy.mockReset(); fetchSpy.mockReset();
fetchSpy.mockResolvedValueOnce({}).mockResolvedValueOnce({}); fetchSpy.mockResolvedValueOnce({}).mockResolvedValueOnce({});
@@ -64,6 +68,12 @@ describe('Utils tests', () => {
expect(await utils.parseIniFile('none')).toBe('none'); expect(await utils.parseIniFile('none')).toBe('none');
expect(await utils.parseIniFile('php.ini-production')).toBe('production'); expect(await utils.parseIniFile('php.ini-production')).toBe('production');
expect(await utils.parseIniFile('php.ini-development')).toBe('development'); expect(await utils.parseIniFile('php.ini-development')).toBe('development');
expect(await utils.parseIniFile('/etc/php.ini-production')).toBe(
'production'
);
expect(await utils.parseIniFile('/a-b/php.ini-development')).toBe(
'development'
);
expect(await utils.parseIniFile('invalid')).toBe('production'); expect(await utils.parseIniFile('invalid')).toBe('production');
}); });
@@ -93,6 +103,26 @@ describe('Utils tests', () => {
expect(await utils.extensionArray('')).toEqual([]); expect(await utils.extensionArray('')).toEqual([]);
expect(await utils.extensionArray(' ')).toEqual([]); expect(await utils.extensionArray(' ')).toEqual([]);
expect(
await utils.extensionArray('apcu, mbstring, \\ pdo_pgsql, posix, session')
).toEqual(['apcu', 'mbstring', 'pdo_pgsql', 'posix', 'session']);
});
it('checking shell helpers', () => {
expect(utils.escapeForShell('a$b`c\\d"e', 'linux')).toBe(
'a\\$b\\`c\\\\d\\"e'
);
expect(utils.escapeForShell('a$b`c"d', 'win32')).toBe('a`$b``c`"d');
expect(utils.safeArg('vendor-pkg/repo@v1.0.0', 'linux')).toBe(
'vendor-pkg/repo@v1.0.0'
);
expect(utils.safeArg('phpcs:>=3.0', 'linux')).toBe('"phpcs:>=3.0"');
expect(utils.safeArg('foo$bar', 'win32')).toBe('"foo`$bar"');
expect(utils.sanitizeShellInput('foo;$(`ls`)bar')).toBe('foolsbar');
expect(utils.sanitizeShellInput('vendor/foo:1.*', true)).toBe(
'vendor/foo:1.'
);
}); });
it('checking INIArray', async () => { it('checking INIArray', async () => {
@@ -286,6 +316,9 @@ describe('Utils tests', () => {
process.env['php-version'] = '8.2'; process.env['php-version'] = '8.2';
expect(await utils.readPHPVersion()).toBe('8.2'); expect(await utils.readPHPVersion()).toBe('8.2');
process.env['php-version'] = 'pre-installed';
expect(await utils.readPHPVersion()).toBe('pre-installed');
delete process.env['php-version-file']; delete process.env['php-version-file'];
delete process.env['php-version']; delete process.env['php-version'];
@@ -295,7 +328,7 @@ describe('Utils tests', () => {
existsSync.mockReturnValue(true); existsSync.mockReturnValue(true);
readFileSync.mockReturnValue('setup-php'); readFileSync.mockReturnValue('setup-php');
expect(await utils.readPHPVersion()).toBe('setup-php'); await expect(utils.readPHPVersion()).rejects.toThrow('Invalid PHP version');
existsSync.mockReturnValueOnce(false).mockReturnValueOnce(true); existsSync.mockReturnValueOnce(false).mockReturnValueOnce(true);
readFileSync.mockReturnValue( readFileSync.mockReturnValue(
@@ -316,6 +349,37 @@ describe('Utils tests', () => {
readFileSync.mockClear(); readFileSync.mockClear();
}); });
it('readPHPVersion rejects unsupported values from each source', async () => {
const existsSync = jest.spyOn(fs, 'existsSync').mockImplementation();
const readFileSync = jest.spyOn(fs, 'readFileSync').mockImplementation();
process.env['php-version'] = 'bogus';
await expect(utils.readPHPVersion()).rejects.toThrow('php-version input');
delete process.env['php-version'];
existsSync.mockReturnValue(true);
readFileSync.mockReturnValue('bogus');
await expect(utils.readPHPVersion()).rejects.toThrow('.php-version');
existsSync.mockReturnValueOnce(false).mockReturnValueOnce(true);
readFileSync.mockReturnValue('{"platform-overrides":{"php":"bogus"}}');
await expect(utils.readPHPVersion()).rejects.toThrow(
'composer.lock platform-overrides.php'
);
existsSync
.mockReturnValueOnce(false)
.mockReturnValueOnce(false)
.mockReturnValueOnce(true);
readFileSync.mockReturnValue('{"config":{"platform":{"php":"bogus"}}}');
await expect(utils.readPHPVersion()).rejects.toThrow(
'composer.json config.platform.php'
);
existsSync.mockClear();
readFileSync.mockClear();
});
it('checking setVariable', async () => { it('checking setVariable', async () => {
let script: string = await utils.setVariable('var', 'command', 'linux'); let script: string = await utils.setVariable('var', 'command', 'linux');
expect(script).toEqual('\nvar="$(command)"\n'); expect(script).toEqual('\nvar="$(command)"\n');

View File

@@ -34,5 +34,5 @@ outputs:
php-version: php-version:
description: 'PHP version in semver format' description: 'PHP version in semver format'
runs: runs:
using: 'node20' using: 'node24'
main: 'dist/index.js' main: 'dist/index.js'

4
dist/index.js vendored

File diff suppressed because one or more lines are too long

View File

@@ -1,39 +1,22 @@
import {fixupConfigRules, fixupPluginRules} from '@eslint/compat';
// eslint-disable-next-line import/no-unresolved
import typescriptEslint from '@typescript-eslint/eslint-plugin'; import typescriptEslint from '@typescript-eslint/eslint-plugin';
import {importX} from 'eslint-plugin-import-x';
import jest from 'eslint-plugin-jest'; import jest from 'eslint-plugin-jest';
import prettierRecommended from 'eslint-plugin-prettier/recommended';
import eslintConfigPrettier from 'eslint-config-prettier';
import globals from 'globals'; import globals from 'globals';
// eslint-disable-next-line import/no-unresolved
import tsParser from '@typescript-eslint/parser'; import tsParser from '@typescript-eslint/parser';
import path from 'node:path';
import {fileURLToPath} from 'node:url';
import js from '@eslint/js'; import js from '@eslint/js';
import {FlatCompat} from '@eslint/eslintrc';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const compat = new FlatCompat({
baseDirectory: __dirname,
recommendedConfig: js.configs.recommended,
allConfig: js.configs.all
});
export default [ export default [
...fixupConfigRules( js.configs.recommended,
compat.extends( ...typescriptEslint.configs['flat/recommended'],
'eslint:recommended', importX.flatConfigs.errors,
'plugin:@typescript-eslint/eslint-recommended', importX.flatConfigs.warnings,
'plugin:@typescript-eslint/recommended', importX.flatConfigs.typescript,
'plugin:import/errors', prettierRecommended,
'plugin:import/warnings', eslintConfigPrettier,
'plugin:import/typescript',
'plugin:prettier/recommended',
'prettier'
)
),
{ {
plugins: { plugins: {
'@typescript-eslint': fixupPluginRules(typescriptEslint),
jest jest
}, },
@@ -46,6 +29,21 @@ export default [
parser: tsParser, parser: tsParser,
ecmaVersion: 2021, ecmaVersion: 2021,
sourceType: 'module' sourceType: 'module'
},
settings: {
'import-x/resolver': {
typescript: {
alwaysTryTypes: true,
project: './tsconfig.json'
},
node: {
extensions: ['.js', '.ts']
}
},
'import-x/parsers': {
'@typescript-eslint/parser': ['.ts']
}
} }
} }
]; ];

View File

@@ -8,7 +8,7 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
php-versions: ['7.4', '8.0', '8.1'] php-versions: ['8.3', '8.4', '8.5']
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v6 uses: actions/checkout@v6
@@ -23,7 +23,7 @@ jobs:
id: composer-cache id: composer-cache
run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
- uses: actions/cache@v3 - uses: actions/cache@v5
with: with:
path: ${{ steps.composer-cache.outputs.dir }} path: ${{ steps.composer-cache.outputs.dir }}
# Use composer.json for key, if composer.lock is not committed. # Use composer.json for key, if composer.lock is not committed.

View File

@@ -4,6 +4,9 @@ on: [push, pull_request]
jobs: jobs:
blackfire-player: blackfire-player:
name: Blackfire (PHP ${{ matrix.php-versions }}) name: Blackfire (PHP ${{ matrix.php-versions }})
defaults:
run:
shell: bash
# Add your Blackfire credentials securely using GitHub Secrets # Add your Blackfire credentials securely using GitHub Secrets
env: env:
BLACKFIRE_SERVER_ID: ${{ secrets.BLACKFIRE_SERVER_ID }} BLACKFIRE_SERVER_ID: ${{ secrets.BLACKFIRE_SERVER_ID }}
@@ -14,9 +17,9 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
operating-system: [ubuntu-latest, windows-latest, macos-latest] operating-system: [ubuntu-latest, macos-latest]
php-versions: ['7.4', '8.0', '8.1'] php-versions: ['8.3', '8.4', '8.5']
# blackfire-player supports PHP >= 5.5 # Blackfire Player supports PHP 8.5 and is available on Ubuntu and macOS.
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v6 uses: actions/checkout@v6
@@ -31,6 +34,14 @@ jobs:
tools: blackfire, blackfire-player tools: blackfire, blackfire-player
coverage: none coverage: none
# Refer to https://blackfire.io/docs/player/index#usage - name: Start local endpoint
run: |
php -S 127.0.0.1:8080 > "$RUNNER_TEMP/blackfire-player.log" 2>&1 &
sleep 5
- name: Validate scenario
run: blackfire-player validate scenario.bkf
# Refer to https://docs.blackfire.io/builds-cookbooks/player
- name: Play the scenario - name: Play the scenario
run: blackfire-player run scenario.bkf run: blackfire-player run scenario.bkf --endpoint=http://127.0.0.1:8080

View File

@@ -15,8 +15,8 @@ jobs:
fail-fast: false fail-fast: false
matrix: matrix:
operating-system: [ubuntu-latest, windows-latest, macos-latest] operating-system: [ubuntu-latest, windows-latest, macos-latest]
php-versions: ['7.4', '8.0', '8.1'] php-versions: ['8.3', '8.4', '8.5']
# Blackfire supports PHP >= 5.3 on Ubuntu and macOS, and PHP >= 5.4 on Windows # Blackfire supports the current PHP releases on Ubuntu, macOS, and Windows.
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v6 uses: actions/checkout@v6
@@ -26,12 +26,24 @@ jobs:
uses: shivammathur/setup-php@v2 uses: shivammathur/setup-php@v2
with: with:
php-version: ${{ matrix.php-versions }} php-version: ${{ matrix.php-versions }}
# Setup Blackfire extension and CLI # Setup Blackfire extension and CLI.
extensions: blackfire extensions: blackfire, :xdebug
tools: blackfire tools: blackfire
# Disable Xdebug and PCOV coverage drivers # Disable Xdebug and PCOV coverage drivers
coverage: none coverage: none
# Refer to https://blackfire.io/docs/cookbooks/profiling-cli # Refer to https://blackfire.io/docs/cookbooks/profiling-cli
- name: Profile - name: Profile
run: blackfire run php my-script.php shell: bash
run: |
set +e
output=$(blackfire run php my-script.php 2>&1)
exit_code=$?
printf '%s\n' "$output"
if [ "$exit_code" -ne 0 ]; then
if printf '%s' "$output" | grep -q "upgrade your subscription"; then
echo "Blackfire profiling reached the repository quota limit; treating this as a known non-fatal condition."
exit 0
fi
exit "$exit_code"
fi

View File

@@ -6,7 +6,8 @@ jobs:
tests: tests:
strategy: strategy:
matrix: matrix:
php-versions: ['7.4', '8.0', '8.1'] php-versions: ['8.4', '8.5']
# The latest cakephp/app release resolves dev dependencies that require PHP 8.4+.
runs-on: ubuntu-latest runs-on: ubuntu-latest
# Docs: https://docs.github.com/en/actions/using-containerized-services # Docs: https://docs.github.com/en/actions/using-containerized-services
@@ -37,7 +38,7 @@ jobs:
php-version: ${{ matrix.php-versions }} php-version: ${{ matrix.php-versions }}
# You can also use ext-apcu or ext-memcached instead of ext-redis # You can also use ext-apcu or ext-memcached instead of ext-redis
# Install memcached if using ext-memcached # Install memcached if using ext-memcached
extensions: mbstring, intl, redis, pdo_mysql extensions: mbstring, intl, redis, apcu, pdo_mysql
coverage: pcov coverage: pcov
# Local MySQL service in GitHub hosted environments is disabled by default. # Local MySQL service in GitHub hosted environments is disabled by default.
@@ -50,7 +51,7 @@ jobs:
run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
- name: Cache composer dependencies - name: Cache composer dependencies
uses: actions/cache@v3 uses: actions/cache@v5
with: with:
path: ${{ steps.composer-cache.outputs.dir }} path: ${{ steps.composer-cache.outputs.dir }}
# Use composer.json for key, if composer.lock is not committed. # Use composer.json for key, if composer.lock is not committed.
@@ -61,14 +62,15 @@ jobs:
- name: Install dependencies - name: Install dependencies
run: | run: |
composer install --no-progress --prefer-dist --optimize-autoloader composer install --no-progress --prefer-dist --optimize-autoloader
composer run-script post-install-cmd composer run-script post-install-cmd --no-interaction
# Add a step to run migrations if required # Add a step to run migrations if required
- name: Test with phpunit - name: Test with phpunit
run: vendor/bin/phpunit --coverage-text run: vendor/bin/phpunit --coverage-text
env: env:
REDIS_PORT: ${{ job.services.redis.ports['6379'] }} REDIS_PORT: ${{ job.services.redis.ports['6379'] }}
DB_DSN: "mysql://root:password@127.0.0.1:${{ job.services.mysql.ports['3306'] }}/cakephp?init[]=SET sql_mode = \"STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION\"" DATABASE_URL: "mysql://root:password@127.0.0.1:${{ job.services.mysql.ports['3306'] }}/cakephp?init[]=SET sql_mode = \"STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION\""
DATABASE_TEST_URL: "mysql://root:password@127.0.0.1:${{ job.services.mysql.ports['3306'] }}/cakephp?init[]=SET sql_mode = \"STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION\""
coding-standard: coding-standard:
name: Coding Standard name: Coding Standard
@@ -81,7 +83,7 @@ jobs:
- name: Setup PHP - name: Setup PHP
uses: shivammathur/setup-php@v2 uses: shivammathur/setup-php@v2
with: with:
php-version: '8.1' php-version: '8.5'
extensions: mbstring, intl extensions: mbstring, intl
- name: Get composer cache directory - name: Get composer cache directory
@@ -89,7 +91,7 @@ jobs:
run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
- name: Cache composer dependencies - name: Cache composer dependencies
uses: actions/cache@v3 uses: actions/cache@v5
with: with:
path: ${{ steps.composer-cache.outputs.dir }} path: ${{ steps.composer-cache.outputs.dir }}
# Use composer.json for key, if composer.lock is not committed. # Use composer.json for key, if composer.lock is not committed.
@@ -114,7 +116,7 @@ jobs:
- name: Setup PHP - name: Setup PHP
uses: shivammathur/setup-php@v2 uses: shivammathur/setup-php@v2
with: with:
php-version: '8.1' php-version: '8.5'
extensions: mbstring, intl extensions: mbstring, intl
tools: phpstan tools: phpstan
@@ -123,7 +125,7 @@ jobs:
run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
- name: Cache composer dependencies - name: Cache composer dependencies
uses: actions/cache@v3 uses: actions/cache@v5
with: with:
path: ${{ steps.composer-cache.outputs.dir }} path: ${{ steps.composer-cache.outputs.dir }}
# Use composer.json for key, if composer.lock is not committed. # Use composer.json for key, if composer.lock is not committed.
@@ -132,7 +134,9 @@ jobs:
restore-keys: ${{ runner.os }}-composer- restore-keys: ${{ runner.os }}-composer-
- name: Install dependencies - name: Install dependencies
run: composer install --no-progress --prefer-dist --optimize-autoloader run: |
composer install --no-progress --prefer-dist --optimize-autoloader
composer run-script post-install-cmd --no-interaction
- name: Static Analysis using PHPStan - name: Static Analysis using PHPStan
run: phpstan analyse --no-progress src/ run: phpstan analyse --no-progress src/

View File

@@ -5,8 +5,10 @@ on: [push, pull_request]
jobs: jobs:
tests: tests:
strategy: strategy:
fail-fast: false
matrix: matrix:
php-versions: ['7.4', '8.0', '8.1'] php-versions: ['8.4', '8.5']
# The latest cakephp/app release resolves dev dependencies that require PHP 8.4+.
runs-on: ubuntu-latest runs-on: ubuntu-latest
# Docs: https://docs.github.com/en/actions/using-containerized-services # Docs: https://docs.github.com/en/actions/using-containerized-services
@@ -37,7 +39,7 @@ jobs:
php-version: ${{ matrix.php-versions }} php-version: ${{ matrix.php-versions }}
# You can also use ext-apcu or ext-memcached instead of ext-redis # You can also use ext-apcu or ext-memcached instead of ext-redis
# Install memcached if using ext-memcached # Install memcached if using ext-memcached
extensions: mbstring, intl, redis, pdo_pgsql extensions: mbstring, intl, redis, apcu, pdo_pgsql
coverage: pcov coverage: pcov
# Local PostgreSQL service in GitHub hosted environments is disabled by default. # Local PostgreSQL service in GitHub hosted environments is disabled by default.
@@ -50,7 +52,7 @@ jobs:
run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
- name: Cache composer dependencies - name: Cache composer dependencies
uses: actions/cache@v3 uses: actions/cache@v5
with: with:
path: ${{ steps.composer-cache.outputs.dir }} path: ${{ steps.composer-cache.outputs.dir }}
# Use composer.json for key, if composer.lock is not committed. # Use composer.json for key, if composer.lock is not committed.
@@ -61,14 +63,15 @@ jobs:
- name: Install dependencies - name: Install dependencies
run: | run: |
composer install --no-progress --prefer-dist --optimize-autoloader composer install --no-progress --prefer-dist --optimize-autoloader
composer run-script post-install-cmd composer run-script post-install-cmd --no-interaction
# Add a step to run migrations if required # Add a step to run migrations if required
- name: Test with phpunit - name: Test with phpunit
run: vendor/bin/phpunit --coverage-text run: vendor/bin/phpunit --coverage-text
env: env:
REDIS_PORT: ${{ job.services.redis.ports['6379'] }} REDIS_PORT: ${{ job.services.redis.ports['6379'] }}
DB_DSN: postgres://postgres@127.0.0.1:${{ job.services.postgres.ports['5432'] }}/postgres DATABASE_URL: postgres://postgres:postgres@127.0.0.1:${{ job.services.postgres.ports['5432'] }}/postgres?encoding=UTF8
DATABASE_TEST_URL: postgres://postgres:postgres@127.0.0.1:${{ job.services.postgres.ports['5432'] }}/postgres?encoding=UTF8
coding-standard: coding-standard:
name: Coding Standard name: Coding Standard
@@ -81,15 +84,15 @@ jobs:
- name: Setup PHP - name: Setup PHP
uses: shivammathur/setup-php@v2 uses: shivammathur/setup-php@v2
with: with:
php-version: '8.1' php-version: '8.5'
extensions: mbstring, intl extensions: mbstring, intl, apcu
- name: Get composer cache directory - name: Get composer cache directory
id: composer-cache id: composer-cache
run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
- name: Cache composer dependencies - name: Cache composer dependencies
uses: actions/cache@v3 uses: actions/cache@v5
with: with:
path: ${{ steps.composer-cache.outputs.dir }} path: ${{ steps.composer-cache.outputs.dir }}
# Use composer.json for key, if composer.lock is not committed. # Use composer.json for key, if composer.lock is not committed.
@@ -114,8 +117,8 @@ jobs:
- name: Setup PHP - name: Setup PHP
uses: shivammathur/setup-php@v2 uses: shivammathur/setup-php@v2
with: with:
php-version: '8.1' php-version: '8.5'
extensions: mbstring, intl extensions: mbstring, intl, apcu
tools: phpstan tools: phpstan
- name: Get composer cache directory - name: Get composer cache directory
@@ -123,7 +126,7 @@ jobs:
run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
- name: Cache composer dependencies - name: Cache composer dependencies
uses: actions/cache@v3 uses: actions/cache@v5
with: with:
path: ${{ steps.composer-cache.outputs.dir }} path: ${{ steps.composer-cache.outputs.dir }}
# Use composer.json for key, if composer.lock is not committed. # Use composer.json for key, if composer.lock is not committed.
@@ -132,7 +135,9 @@ jobs:
restore-keys: ${{ runner.os }}-composer- restore-keys: ${{ runner.os }}-composer-
- name: Install dependencies - name: Install dependencies
run: composer install --no-progress --prefer-dist --optimize-autoloader run: |
composer install --no-progress --prefer-dist --optimize-autoloader
composer run-script post-install-cmd --no-interaction
- name: Static Analysis using PHPStan - name: Static Analysis using PHPStan
run: phpstan analyse --no-progress src/ run: phpstan analyse --no-progress src/

View File

@@ -7,7 +7,8 @@ jobs:
strategy: strategy:
matrix: matrix:
operating-system: [ubuntu-latest, windows-latest, macos-latest] operating-system: [ubuntu-latest, windows-latest, macos-latest]
php-versions: ['7.4', '8.0', '8.1'] php-versions: ['8.4', '8.5']
# The latest cakephp/app release resolves dev dependencies that require PHP 8.4+.
runs-on: ${{ matrix.operating-system }} runs-on: ${{ matrix.operating-system }}
steps: steps:
- name: Checkout - name: Checkout
@@ -23,10 +24,11 @@ jobs:
- name: Get composer cache directory - name: Get composer cache directory
id: composer-cache id: composer-cache
shell: bash
run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
- name: Cache composer dependencies - name: Cache composer dependencies
uses: actions/cache@v3 uses: actions/cache@v5
with: with:
path: ${{ steps.composer-cache.outputs.dir }} path: ${{ steps.composer-cache.outputs.dir }}
# Use composer.json for key, if composer.lock is not committed. # Use composer.json for key, if composer.lock is not committed.
@@ -37,7 +39,7 @@ jobs:
- name: Install dependencies - name: Install dependencies
run: | run: |
composer install --no-progress --prefer-dist --optimize-autoloader composer install --no-progress --prefer-dist --optimize-autoloader
composer run-script post-install-cmd composer run-script post-install-cmd --no-interaction
- name: Test with phpunit - name: Test with phpunit
run: vendor/bin/phpunit --coverage-text run: vendor/bin/phpunit --coverage-text
@@ -53,14 +55,15 @@ jobs:
- name: Setup PHP - name: Setup PHP
uses: shivammathur/setup-php@v2 uses: shivammathur/setup-php@v2
with: with:
php-version: '8.1' php-version: '8.5'
extensions: mbstring, intl extensions: mbstring, intl
- name: Get composer cache directory - name: Get composer cache directory
id: composer-cache id: composer-cache
shell: bash
run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
- name: Cache composer dependencies - name: Cache composer dependencies
uses: actions/cache@v3 uses: actions/cache@v5
with: with:
path: ${{ steps.composer-cache.outputs.dir }} path: ${{ steps.composer-cache.outputs.dir }}
# Use composer.json for key, if composer.lock is not committed. # Use composer.json for key, if composer.lock is not committed.
@@ -85,16 +88,17 @@ jobs:
- name: Setup PHP - name: Setup PHP
uses: shivammathur/setup-php@v2 uses: shivammathur/setup-php@v2
with: with:
php-version: '8.1' php-version: '8.5'
extensions: mbstring, intl extensions: mbstring, intl
tools: phpstan tools: phpstan
- name: Get composer cache directory - name: Get composer cache directory
id: composer-cache id: composer-cache
shell: bash
run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
- name: Cache composer dependencies - name: Cache composer dependencies
uses: actions/cache@v3 uses: actions/cache@v5
with: with:
path: ${{ steps.composer-cache.outputs.dir }} path: ${{ steps.composer-cache.outputs.dir }}
# Use composer.json for key, if composer.lock is not committed. # Use composer.json for key, if composer.lock is not committed.
@@ -103,7 +107,9 @@ jobs:
restore-keys: ${{ runner.os }}-composer- restore-keys: ${{ runner.os }}-composer-
- name: Install dependencies - name: Install dependencies
run: composer install --no-progress --prefer-dist --optimize-autoloader run: |
composer install --no-progress --prefer-dist --optimize-autoloader
composer run-script post-install-cmd --no-interaction
- name: Static Analysis using PHPStan - name: Static Analysis using PHPStan
run: phpstan analyse --no-progress src/ run: phpstan analyse --no-progress src/

View File

@@ -6,7 +6,7 @@ jobs:
strategy: strategy:
matrix: matrix:
operating-system: [ubuntu-latest, windows-latest, macos-latest] operating-system: [ubuntu-latest, windows-latest, macos-latest]
php-versions: ['7.4', '8.0', '8.1'] php-versions: ['8.3', '8.4', '8.5']
runs-on: ${{ matrix.operating-system }} runs-on: ${{ matrix.operating-system }}
steps: steps:
- name: Checkout - name: Checkout
@@ -17,15 +17,16 @@ jobs:
uses: shivammathur/setup-php@v2 uses: shivammathur/setup-php@v2
with: with:
php-version: ${{ matrix.php-versions }} php-version: ${{ matrix.php-versions }}
extensions: mbstring, intl, curl, dom extensions: mbstring, intl, curl, dom, sqlite3, pdo_sqlite
coverage: xdebug coverage: xdebug
- name: Get composer cache directory - name: Get composer cache directory
id: composer-cache id: composer-cache
run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT shell: bash
run: echo "dir=$(composer config cache-files-dir)" >> "$GITHUB_OUTPUT"
- name: Cache composer dependencies - name: Cache composer dependencies
uses: actions/cache@v3 uses: actions/cache@v5
with: with:
path: ${{ steps.composer-cache.outputs.dir }} path: ${{ steps.composer-cache.outputs.dir }}
# Use composer.json for key, if composer.lock is not committed. # Use composer.json for key, if composer.lock is not committed.

59
examples/drupal.yml Normal file
View File

@@ -0,0 +1,59 @@
# GitHub Action for Drupal 11 composer-managed projects
# Requires drupal/core-dev in require-dev for vendor/bin/phpunit
name: Testing Drupal
on: [push, pull_request]
jobs:
drupal:
name: Drupal (PHP ${{ matrix.php-versions }})
runs-on: ubuntu-latest
env:
SIMPLETEST_BASE_URL: http://127.0.0.1:8080
SIMPLETEST_DB: sqlite://localhost/sites/default/files/.ht.sqlite
BROWSERTEST_OUTPUT_DIRECTORY: /tmp/browser_output
strategy:
fail-fast: false
matrix:
php-versions: ['8.3', '8.4', '8.5']
steps:
- name: Checkout
uses: actions/checkout@v6
# Docs: https://github.com/shivammathur/setup-php
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php-versions }}
extensions: apcu, ctype, curl, dom, gd, iconv, intl, mbstring, pdo_sqlite, simplexml, xml, zip
coverage: none
- name: Get composer cache directory
id: composer-cache
run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
- name: Cache composer dependencies
uses: actions/cache@v5
with:
path: ${{ steps.composer-cache.outputs.dir }}
# Use composer.json for key, if composer.lock is not committed.
# key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }}
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
restore-keys: ${{ runner.os }}-composer-
- name: Install Composer dependencies
run: |
if [ "${{ matrix.php-versions }}" = "8.3" ]; then
composer require --dev drupal/core-dev:11.2.10 doctrine/instantiator:^2.0.0 --no-interaction --no-update
composer update drupal/core-dev doctrine/instantiator --with-all-dependencies --no-interaction --no-progress --prefer-dist --optimize-autoloader
else
composer install --no-interaction --no-progress --prefer-dist --optimize-autoloader
fi
- name: Prepare Drupal test directories
run: mkdir -p web/sites/default/files "$BROWSERTEST_OUTPUT_DIRECTORY"
- name: Start Drupal web server
run: php -S 127.0.0.1:8080 -t web >/tmp/php-server.log 2>&1 &
- name: Test with phpunit
# Adjust the test path to match your custom modules or themes.
run: vendor/bin/phpunit -c web/core web/modules/custom

View File

@@ -1,12 +1,12 @@
# GitHub Action for Laminas framework MVC projects # GitHub Action for Laminas framework MVC projects
name: Testing Zend Framework name: Testing Laminas MVC
on: [push, pull_request] on: [push, pull_request]
jobs: jobs:
build: build:
strategy: strategy:
matrix: matrix:
operating-system: [ubuntu-latest, windows-latest, macos-latest] operating-system: [ubuntu-latest, windows-latest, macos-latest]
php-versions: ['7.4', '8.0', '8.1'] php-versions: ['8.1', '8.2', '8.3']
runs-on: ${{ matrix.operating-system }} runs-on: ${{ matrix.operating-system }}
steps: steps:
- name: Checkout - name: Checkout
@@ -21,10 +21,11 @@ jobs:
- name: Get composer cache directory - name: Get composer cache directory
id: composer-cache id: composer-cache
shell: bash
run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
- name: Cache composer dependencies - name: Cache composer dependencies
uses: actions/cache@v3 uses: actions/cache@v5
with: with:
path: ${{ steps.composer-cache.outputs.dir }} path: ${{ steps.composer-cache.outputs.dir }}
# Use composer.json for key, if composer.lock is not committed. # Use composer.json for key, if composer.lock is not committed.

View File

@@ -6,11 +6,13 @@ jobs:
name: Laravel (PHP ${{ matrix.php-versions }}) name: Laravel (PHP ${{ matrix.php-versions }})
runs-on: ubuntu-latest runs-on: ubuntu-latest
env: env:
DB_CONNECTION: mysql
DB_HOST: 127.0.0.1
DB_DATABASE: laravel DB_DATABASE: laravel
DB_USERNAME: root DB_USERNAME: root
DB_PASSWORD: password DB_PASSWORD: password
BROADCAST_DRIVER: log BROADCAST_CONNECTION: log
CACHE_DRIVER: redis CACHE_STORE: redis
QUEUE_CONNECTION: redis QUEUE_CONNECTION: redis
SESSION_DRIVER: redis SESSION_DRIVER: redis
@@ -34,7 +36,7 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
php-versions: ['7.4', '8.0', '8.1'] php-versions: ['8.3', '8.4', '8.5']
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v6 uses: actions/checkout@v6
@@ -57,7 +59,7 @@ jobs:
run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
- name: Cache composer dependencies - name: Cache composer dependencies
uses: actions/cache@v3 uses: actions/cache@v5
with: with:
path: ${{ steps.composer-cache.outputs.dir }} path: ${{ steps.composer-cache.outputs.dir }}
# Use composer.json for key, if composer.lock is not committed. # Use composer.json for key, if composer.lock is not committed.

View File

@@ -11,7 +11,7 @@ jobs:
QUEUE_CONNECTION: redis QUEUE_CONNECTION: redis
SESSION_DRIVER: redis SESSION_DRIVER: redis
DB_CONNECTION: pgsql DB_CONNECTION: pgsql
DB_HOST: localhost DB_HOST: 127.0.0.1
DB_PASSWORD: postgres DB_PASSWORD: postgres
DB_USERNAME: postgres DB_USERNAME: postgres
DB_DATABASE: postgres DB_DATABASE: postgres
@@ -36,7 +36,7 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
php-versions: ['7.4', '8.0', '8.1'] php-versions: ['8.3', '8.4', '8.5']
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v6 uses: actions/checkout@v6
@@ -59,7 +59,7 @@ jobs:
run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
- name: Cache composer dependencies - name: Cache composer dependencies
uses: actions/cache@v3 uses: actions/cache@v5
with: with:
path: ${{ steps.composer-cache.outputs.dir }} path: ${{ steps.composer-cache.outputs.dir }}
# Use composer.json for key, if composer.lock is not committed. # Use composer.json for key, if composer.lock is not committed.
@@ -81,11 +81,11 @@ jobs:
- name: Run Migration - name: Run Migration
run: php artisan migrate -v run: php artisan migrate -v
env: env:
DB_PORT: ${{ job.services.postgres.ports[5432] }} DB_PORT: ${{ job.services.postgres.ports['5432'] }}
REDIS_PORT: ${{ job.services.redis.ports['6379'] }} REDIS_PORT: ${{ job.services.redis.ports['6379'] }}
- name: Test with phpunit - name: Test with phpunit
run: vendor/bin/phpunit --coverage-text run: vendor/bin/phpunit --coverage-text
env: env:
DB_PORT: ${{ job.services.postgres.ports[5432] }} DB_PORT: ${{ job.services.postgres.ports['5432'] }}
REDIS_PORT: ${{ job.services.redis.ports['6379'] }} REDIS_PORT: ${{ job.services.redis.ports['6379'] }}

View File

@@ -9,7 +9,7 @@ jobs:
fail-fast: false fail-fast: false
matrix: matrix:
operating-system: [ubuntu-latest, windows-latest, macos-latest] operating-system: [ubuntu-latest, windows-latest, macos-latest]
php-versions: ['7.4', '8.0', '8.1'] php-versions: ['8.3', '8.4', '8.5']
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v6 uses: actions/checkout@v6
@@ -24,10 +24,11 @@ jobs:
- name: Get composer cache directory - name: Get composer cache directory
id: composer-cache id: composer-cache
shell: bash
run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
- name: Cache composer dependencies - name: Cache composer dependencies
uses: actions/cache@v3 uses: actions/cache@v5
with: with:
path: ${{ steps.composer-cache.outputs.dir }} path: ${{ steps.composer-cache.outputs.dir }}
# Use composer.json for key, if composer.lock is not committed. # Use composer.json for key, if composer.lock is not committed.

View File

@@ -34,7 +34,7 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
php-versions: ['7.4', '8.0', '8.1'] php-versions: ['8.3', '8.4', '8.5']
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v6 uses: actions/checkout@v6
@@ -57,7 +57,7 @@ jobs:
run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
- name: Cache composer dependencies - name: Cache composer dependencies
uses: actions/cache@v3 uses: actions/cache@v5
with: with:
path: ${{ steps.composer-cache.outputs.dir }} path: ${{ steps.composer-cache.outputs.dir }}
# Use composer.json for key, if composer.lock is not committed. # Use composer.json for key, if composer.lock is not committed.
@@ -83,7 +83,7 @@ jobs:
REDIS_PORT: ${{ job.services.redis.ports['6379'] }} REDIS_PORT: ${{ job.services.redis.ports['6379'] }}
- name: Test with phpunit - name: Test with phpunit
run: vendor/bin/phpunit --coverage-text run: vendor/bin/phpunit --coverage-text --coverage-filter app
env: env:
DB_PORT: ${{ job.services.mysql.ports['3306'] }} DB_PORT: ${{ job.services.mysql.ports['3306'] }}
REDIS_PORT: ${{ job.services.redis.ports['6379'] }} REDIS_PORT: ${{ job.services.redis.ports['6379'] }}

View File

@@ -11,7 +11,7 @@ jobs:
QUEUE_CONNECTION: redis QUEUE_CONNECTION: redis
SESSION_DRIVER: redis SESSION_DRIVER: redis
DB_CONNECTION: pgsql DB_CONNECTION: pgsql
DB_HOST: localhost DB_HOST: 127.0.0.1
DB_PASSWORD: postgres DB_PASSWORD: postgres
DB_USERNAME: postgres DB_USERNAME: postgres
DB_DATABASE: postgres DB_DATABASE: postgres
@@ -36,7 +36,7 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
php-versions: ['7.4', '8.0', '8.1'] php-versions: ['8.3', '8.4', '8.5']
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v6 uses: actions/checkout@v6
@@ -47,7 +47,7 @@ jobs:
with: with:
php-version: ${{ matrix.php-versions }} php-version: ${{ matrix.php-versions }}
extensions: mbstring, dom, fileinfo, pgsql extensions: mbstring, dom, fileinfo, pgsql
coverage: xdebug coverage: none
# Local PostgreSQL service in GitHub hosted environments is disabled by default. # Local PostgreSQL service in GitHub hosted environments is disabled by default.
# If you are using it instead of service containers, make sure you start it. # If you are using it instead of service containers, make sure you start it.
@@ -59,7 +59,7 @@ jobs:
run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
- name: Cache composer dependencies - name: Cache composer dependencies
uses: actions/cache@v3 uses: actions/cache@v5
with: with:
path: ${{ steps.composer-cache.outputs.dir }} path: ${{ steps.composer-cache.outputs.dir }}
# Use composer.json for key, if composer.lock is not committed. # Use composer.json for key, if composer.lock is not committed.
@@ -81,11 +81,11 @@ jobs:
- name: Run Migration - name: Run Migration
run: php artisan migrate -v run: php artisan migrate -v
env: env:
DB_PORT: ${{ job.services.postgres.ports[5432] }} DB_PORT: ${{ job.services.postgres.ports['5432'] }}
REDIS_PORT: ${{ job.services.redis.ports['6379'] }} REDIS_PORT: ${{ job.services.redis.ports['6379'] }}
- name: Test with phpunit - name: Test with phpunit
run: vendor/bin/phpunit --coverage-text run: vendor/bin/phpunit
env: env:
DB_PORT: ${{ job.services.postgres.ports[5432] }} DB_PORT: ${{ job.services.postgres.ports['5432'] }}
REDIS_PORT: ${{ job.services.redis.ports['6379'] }} REDIS_PORT: ${{ job.services.redis.ports['6379'] }}

View File

@@ -9,7 +9,7 @@ jobs:
fail-fast: false fail-fast: false
matrix: matrix:
operating-system: [ubuntu-latest, windows-latest, macos-latest] operating-system: [ubuntu-latest, windows-latest, macos-latest]
php-versions: ['7.4', '8.0', '8.1'] php-versions: ['8.3', '8.4', '8.5']
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v6 uses: actions/checkout@v6
@@ -24,10 +24,11 @@ jobs:
- name: Get composer cache directory - name: Get composer cache directory
id: composer-cache id: composer-cache
shell: bash
run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
- name: Cache composer dependencies - name: Cache composer dependencies
uses: actions/cache@v3 uses: actions/cache@v5
with: with:
path: ${{ steps.composer-cache.outputs.dir }} path: ${{ steps.composer-cache.outputs.dir }}
# Use composer.json for key, if composer.lock is not committed. # Use composer.json for key, if composer.lock is not committed.
@@ -42,4 +43,4 @@ jobs:
run: php -r "file_exists('.env') || copy('.env.example', '.env');" run: php -r "file_exists('.env') || copy('.env.example', '.env');"
- name: Test with phpunit - name: Test with phpunit
run: vendor/bin/phpunit --coverage-text run: vendor/bin/phpunit --coverage-text --coverage-filter app

View File

@@ -11,7 +11,7 @@ jobs:
env: env:
DB_ADAPTER: mysql DB_ADAPTER: mysql
DB_HOST: 127.0.0.1 DB_HOST: 127.0.0.1
DB_NAME: phalcon DB_NAME: vokuro
DB_USERNAME: root DB_USERNAME: root
DB_PASSWORD: password DB_PASSWORD: password
CODECEPTION_URL: 127.0.0.1 CODECEPTION_URL: 127.0.0.1
@@ -20,11 +20,11 @@ jobs:
# Docs: https://docs.github.com/en/actions/using-containerized-services # Docs: https://docs.github.com/en/actions/using-containerized-services
services: services:
mysql: mysql:
image: mysql:latest image: mysql:5.7
env: env:
MYSQL_ALLOW_EMPTY_PASSWORD: false MYSQL_ALLOW_EMPTY_PASSWORD: false
MYSQL_ROOT_PASSWORD: password MYSQL_ROOT_PASSWORD: password
MYSQL_DATABASE: phalcon MYSQL_DATABASE: vokuro
ports: ports:
- 3306/tcp - 3306/tcp
options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3 options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3
@@ -32,8 +32,6 @@ jobs:
fail-fast: false fail-fast: false
matrix: matrix:
php-versions: ['7.2', '7.3', '7.4'] php-versions: ['7.2', '7.3', '7.4']
# For phalcon 3.x, use
# php-versions: ['7.0', '7.1', '7.2', '7.3']
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v6 uses: actions/checkout@v6
@@ -43,7 +41,7 @@ jobs:
uses: shivammathur/setup-php@v2 uses: shivammathur/setup-php@v2
with: with:
php-version: ${{ matrix.php-versions }} php-version: ${{ matrix.php-versions }}
# Use phalcon3 for the phalcon 3.x. # Use phalcon4 for the latest compatible Vokuro sample release.
extensions: mbstring, dom, zip, phalcon4, mysql extensions: mbstring, dom, zip, phalcon4, mysql
coverage: xdebug coverage: xdebug
@@ -57,7 +55,7 @@ jobs:
run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
- name: Cache composer dependencies - name: Cache composer dependencies
uses: actions/cache@v3 uses: actions/cache@v5
with: with:
path: ${{ steps.composer-cache.outputs.dir }} path: ${{ steps.composer-cache.outputs.dir }}
# Use composer.json for key, if composer.lock is not committed. # Use composer.json for key, if composer.lock is not committed.
@@ -73,7 +71,6 @@ jobs:
- name: Run Migration - name: Run Migration
run: | run: |
if [ ! -e phinx.yml ]; then vendor/bin/phinx init; fi
vendor/bin/phinx migrate vendor/bin/phinx migrate
vendor/bin/phinx seed:run vendor/bin/phinx seed:run
env: env:

View File

@@ -11,21 +11,18 @@ jobs:
env: env:
DB_ADAPTER: pgsql DB_ADAPTER: pgsql
DB_HOST: 127.0.0.1 DB_HOST: 127.0.0.1
DB_NAME: postgres DB_NAME: vokuro
DB_USERNAME: postgres DB_USERNAME: postgres
DB_PASSWORD: postgres DB_PASSWORD: postgres
CODECEPTION_URL: 127.0.0.1 CODECEPTION_URL: 127.0.0.1
CODECEPTION_PORT: 8888 CODECEPTION_PORT: 8888
DB_CONNECTION: pgsql
# Docs: https://docs.github.com/en/actions/using-containerized-services
services: services:
postgres: postgres:
image: postgres:latest image: postgres:latest
env: env:
POSTGRES_USER: postgres POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres POSTGRES_PASSWORD: postgres
POSTGRES_DB: postgres POSTGRES_DB: vokuro
ports: ports:
- 5432/tcp - 5432/tcp
options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 3 options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 3
@@ -33,8 +30,6 @@ jobs:
fail-fast: false fail-fast: false
matrix: matrix:
php-versions: ['7.2', '7.3', '7.4'] php-versions: ['7.2', '7.3', '7.4']
# For phalcon 3.x, use
# php-versions: ['7.0', '7.1', '7.2', '7.3']
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v6 uses: actions/checkout@v6
@@ -44,41 +39,40 @@ jobs:
uses: shivammathur/setup-php@v2 uses: shivammathur/setup-php@v2
with: with:
php-version: ${{ matrix.php-versions }} php-version: ${{ matrix.php-versions }}
# Use phalcon3 for the phalcon 3.x
extensions: mbstring, dom, zip, phalcon4, pgsql extensions: mbstring, dom, zip, phalcon4, pgsql
coverage: xdebug coverage: xdebug
# Local PostgreSQL service in GitHub hosted environments is disabled by default.
# If you are using it instead of service containers, make sure you start it.
# - name: Start postgresql service
# run: sudo systemctl start postgresql.service
- name: Get composer cache directory - name: Get composer cache directory
id: composer-cache id: composer-cache
run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
- name: Cache composer dependencies - name: Cache composer dependencies
uses: actions/cache@v3 uses: actions/cache@v5
with: with:
path: ${{ steps.composer-cache.outputs.dir }} path: ${{ steps.composer-cache.outputs.dir }}
# Use composer.json for key, if composer.lock is not committed.
# key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }}
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
restore-keys: ${{ runner.os }}-composer- restore-keys: ${{ runner.os }}-composer-
- name: Install Composer dependencies - name: Install Composer dependencies
run: composer install --no-progress --prefer-dist --optimize-autoloader run: composer install --no-progress --prefer-dist --optimize-autoloader --no-security-blocking
- name: Prepare the application - name: Prepare the application
run: php -r "file_exists('.env') || copy('.env.example', '.env');" run: |
php -r "file_exists('.env') || copy('.env.example', '.env');"
mkdir -p var/cache/acl var/cache/metaData var/cache/session var/cache/volt var/logs
chmod -R 777 var/cache var/logs
- name: Run Migration - name: Run Migration
run: | run: |
if [ ! -e phinx.yml ]; then vendor/bin/phinx init; fi
vendor/bin/phinx migrate vendor/bin/phinx migrate
vendor/bin/phinx seed:run vendor/bin/phinx seed:run
env: env:
DB_PORT: ${{ job.services.postgres.ports['5432'] }} DB_PORT: ${{ job.services.postgres.ports['5432'] }}
- name: Run Tests - name: Run Tests
run: | run: |
(cd public && nohup php -S $CODECEPTION_URL:$CODECEPTION_PORT > phalcon.log 2>&1 &) (cd public && nohup php -S $CODECEPTION_URL:$CODECEPTION_PORT > vokuro.log 2>&1 &)
vendor/bin/codecept build vendor/bin/codecept build
vendor/bin/codecept run vendor/bin/codecept run
env: env:
DB_PORT: ${{ job.services.postgres.ports['5432'] }} DB_PORT: ${{ job.services.postgres.ports['5432'] }}

View File

@@ -8,8 +8,8 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
php-versions: ['7.4', '8.0', '8.1'] php-versions: ['8.3', '8.4', '8.5']
node-versions: ['16'] node-versions: ['20']
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v6 uses: actions/checkout@v6
@@ -33,7 +33,7 @@ jobs:
id: yarn-cache id: yarn-cache
run: echo "dir=$(yarn cache dir)" >> $GITHUB_OUTPUT run: echo "dir=$(yarn cache dir)" >> $GITHUB_OUTPUT
- uses: actions/cache@v3 - uses: actions/cache@v5
with: with:
path: ${{ steps.yarn-cache.outputs.dir }} path: ${{ steps.yarn-cache.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
@@ -44,7 +44,7 @@ jobs:
run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
- name: Cache composer dependencies - name: Cache composer dependencies
uses: actions/cache@v3 uses: actions/cache@v5
with: with:
path: ${{ steps.composer-cache.outputs.dir }} path: ${{ steps.composer-cache.outputs.dir }}
# Use composer.json for key, if composer.lock is not committed. # Use composer.json for key, if composer.lock is not committed.
@@ -53,17 +53,10 @@ jobs:
restore-keys: ${{ runner.os }}-composer- restore-keys: ${{ runner.os }}-composer-
- name: Install yarn dependencies - name: Install yarn dependencies
run: yarn -V run: yarn install --frozen-lockfile --non-interactive
- name: Install Composer dependencies - name: Install Composer dependencies
run: composer install --no-progress --prefer-dist --optimize-autoloader run: composer install --no-progress --prefer-dist --optimize-autoloader
- name: Yarn test and build - name: Yarn build
run: | run: yarn build
yarn run test
yarn run build
yarn run rmdist
yarn run "build:production"
- name: PHP test
run: composer test

View File

@@ -6,7 +6,7 @@ jobs:
strategy: strategy:
matrix: matrix:
operating-system: [ubuntu-latest, windows-latest, macos-latest] operating-system: [ubuntu-latest, windows-latest, macos-latest]
php-versions: ['7.4', '8.0', '8.1'] php-versions: ['8.3', '8.4', '8.5']
runs-on: ${{ matrix.operating-system }} runs-on: ${{ matrix.operating-system }}
steps: steps:
- name: Checkout - name: Checkout
@@ -22,10 +22,11 @@ jobs:
- name: Get composer cache directory - name: Get composer cache directory
id: composer-cache id: composer-cache
shell: bash
run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
- name: Cache composer dependencies - name: Cache composer dependencies
uses: actions/cache@v3 uses: actions/cache@v5
with: with:
path: ${{ steps.composer-cache.outputs.dir }} path: ${{ steps.composer-cache.outputs.dir }}
# Use composer.json for key, if composer.lock is not committed. # Use composer.json for key, if composer.lock is not committed.

View File

@@ -5,11 +5,13 @@ jobs:
symfony: symfony:
name: Symfony (PHP ${{ matrix.php-versions }}) name: Symfony (PHP ${{ matrix.php-versions }})
runs-on: ubuntu-latest runs-on: ubuntu-latest
env:
APP_ENV: test
# Docs: https://docs.github.com/en/actions/using-containerized-services # Docs: https://docs.github.com/en/actions/using-containerized-services
services: services:
mysql: mysql:
image: mysql:latest image: mysql:8.4
env: env:
MYSQL_ALLOW_EMPTY_PASSWORD: false MYSQL_ALLOW_EMPTY_PASSWORD: false
MYSQL_ROOT_PASSWORD: symfony MYSQL_ROOT_PASSWORD: symfony
@@ -20,7 +22,7 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
php-versions: ['7.4', '8.0', '8.1'] php-versions: ['8.4', '8.5', '8.6']
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v6 uses: actions/checkout@v6
@@ -30,7 +32,6 @@ jobs:
uses: shivammathur/setup-php@v2 uses: shivammathur/setup-php@v2
with: with:
php-version: ${{ matrix.php-versions }} php-version: ${{ matrix.php-versions }}
tools: phpunit-bridge
extensions: mbstring, xml, ctype, iconv, intl, pdo_sqlite, mysql extensions: mbstring, xml, ctype, iconv, intl, pdo_sqlite, mysql
coverage: xdebug coverage: xdebug
@@ -44,7 +45,7 @@ jobs:
run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
- name: Cache composer dependencies - name: Cache composer dependencies
uses: actions/cache@v3 uses: actions/cache@v5
with: with:
path: ${{ steps.composer-cache.outputs.dir }} path: ${{ steps.composer-cache.outputs.dir }}
# Use composer.json for key, if composer.lock is not committed. # Use composer.json for key, if composer.lock is not committed.
@@ -53,18 +54,17 @@ jobs:
restore-keys: ${{ runner.os }}-composer- restore-keys: ${{ runner.os }}-composer-
- name: Install Composer dependencies - name: Install Composer dependencies
run: composer install --no-progress --prefer-dist --optimize-autoloader run: composer install --no-interaction --no-progress --prefer-dist --optimize-autoloader
- name: Run Migration - name: Prepare database
run: | run: |
composer require --dev symfony/orm-pack php bin/console doctrine:database:create --if-not-exists --no-interaction
php bin/console doctrine:schema:update --force || echo "No migrations found or schema update failed" php bin/console doctrine:schema:create --no-interaction
php bin/console doctrine:migrations:migrate || echo "No migrations found or migration failed" php bin/console doctrine:fixtures:load --no-interaction
env: env:
DATABASE_URL: mysql://root:symfony@127.0.0.1:${{ job.services.mysql.ports['3306'] }}/symfony DATABASE_URL: mysql://root:symfony@127.0.0.1:${{ job.services.mysql.ports['3306'] }}/symfony?serverVersion=8.4&charset=utf8mb4
- name: Install PHPUnit
run: simple-phpunit install
- name: Run tests - name: Run tests
run: simple-phpunit --coverage-text run: php bin/phpunit --coverage-text
env:
DATABASE_URL: mysql://root:symfony@127.0.0.1:${{ job.services.mysql.ports['3306'] }}/symfony?serverVersion=8.4&charset=utf8mb4

View File

@@ -5,11 +5,13 @@ jobs:
symfony: symfony:
name: Symfony (PHP ${{ matrix.php-versions }}) name: Symfony (PHP ${{ matrix.php-versions }})
runs-on: ubuntu-latest runs-on: ubuntu-latest
env:
APP_ENV: test
# Docs: https://docs.github.com/en/actions/using-containerized-services # Docs: https://docs.github.com/en/actions/using-containerized-services
services: services:
postgres: postgres:
image: postgres:latest image: postgres:16
env: env:
POSTGRES_USER: postgres POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres POSTGRES_PASSWORD: postgres
@@ -20,7 +22,7 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
php-versions: ['7.4', '8.0', '8.1'] php-versions: ['8.4', '8.5', '8.6']
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v6 uses: actions/checkout@v6
@@ -30,8 +32,7 @@ jobs:
uses: shivammathur/setup-php@v2 uses: shivammathur/setup-php@v2
with: with:
php-version: ${{ matrix.php-versions }} php-version: ${{ matrix.php-versions }}
tools: phpunit-bridge extensions: mbstring, xml, ctype, iconv, intl, pdo_pgsql, pgsql
extensions: mbstring, xml, ctype, iconv, intl, pdo_sqlite, pgsql
coverage: xdebug coverage: xdebug
# Local PostgreSQL service in GitHub hosted environments is disabled by default. # Local PostgreSQL service in GitHub hosted environments is disabled by default.
@@ -44,7 +45,7 @@ jobs:
run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
- name: Cache composer dependencies - name: Cache composer dependencies
uses: actions/cache@v3 uses: actions/cache@v5
with: with:
path: ${{ steps.composer-cache.outputs.dir }} path: ${{ steps.composer-cache.outputs.dir }}
# Use composer.json for key, if composer.lock is not committed. # Use composer.json for key, if composer.lock is not committed.
@@ -55,16 +56,15 @@ jobs:
- name: Install Composer dependencies - name: Install Composer dependencies
run: composer install --no-progress --prefer-dist --optimize-autoloader run: composer install --no-progress --prefer-dist --optimize-autoloader
- name: Run Migration - name: Prepare database
run: | run: |
composer require --dev symfony/orm-pack php bin/console doctrine:database:create --if-not-exists --no-interaction
php bin/console doctrine:schema:update --force || echo "No migrations found or schema update failed" php bin/console doctrine:schema:create --no-interaction
php bin/console doctrine:migrations:migrate || echo "No migrations found or migration failed" php bin/console doctrine:fixtures:load --no-interaction
env: env:
DATABASE_URL: postgres://postgres:postgres@127.0.0.1:${{ job.services.postgres.ports[5432] }}/postgres?charset=UTF-8 DATABASE_URL: postgresql://postgres:postgres@127.0.0.1:${{ job.services.postgres.ports[5432] }}/postgres?serverVersion=16.0.0&charset=utf8
- name: Install PHPUnit
run: simple-phpunit install
- name: Run tests - name: Run tests
run: simple-phpunit --coverage-text run: php bin/phpunit --coverage-text
env:
DATABASE_URL: postgresql://postgres:postgres@127.0.0.1:${{ job.services.postgres.ports[5432] }}/postgres?serverVersion=16.0.0&charset=utf8

View File

@@ -9,7 +9,7 @@ jobs:
fail-fast: false fail-fast: false
matrix: matrix:
operating-system: [ubuntu-latest, windows-latest, macos-latest] operating-system: [ubuntu-latest, windows-latest, macos-latest]
php-versions: ['7.4', '8.0', '8.1'] php-versions: ['8.4', '8.5', '8.6']
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v6 uses: actions/checkout@v6
@@ -19,16 +19,16 @@ jobs:
uses: shivammathur/setup-php@v2 uses: shivammathur/setup-php@v2
with: with:
php-version: ${{ matrix.php-versions }} php-version: ${{ matrix.php-versions }}
tools: phpunit-bridge extensions: mbstring, xml, ctype, iconv, intl, pdo_sqlite, zip
extensions: mbstring, xml, ctype, iconv, intl, pdo_sqlite
coverage: xdebug coverage: xdebug
- name: Get composer cache directory - name: Get composer cache directory
id: composer-cache id: composer-cache
shell: bash
run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
- name: Cache composer dependencies - name: Cache composer dependencies
uses: actions/cache@v3 uses: actions/cache@v5
with: with:
path: ${{ steps.composer-cache.outputs.dir }} path: ${{ steps.composer-cache.outputs.dir }}
# Use composer.json for key, if composer.lock is not committed. # Use composer.json for key, if composer.lock is not committed.
@@ -39,8 +39,5 @@ jobs:
- name: Install Composer dependencies - name: Install Composer dependencies
run: composer install --no-progress --prefer-dist --optimize-autoloader run: composer install --no-progress --prefer-dist --optimize-autoloader
- name: Install PHPUnit
run: simple-phpunit install
- name: Run tests - name: Run tests
run: simple-phpunit --coverage-text run: ./bin/phpunit --coverage-text

68
examples/wordpress.yml Normal file
View File

@@ -0,0 +1,68 @@
# GitHub Action for WordPress plugins
# Tested with files scaffolded by wp scaffold plugin-tests --ci=github
# Requires phpunit/phpunit and yoast/phpunit-polyfills in require-dev for vendor/bin/phpunit
name: Testing WordPress
on: [push, pull_request]
jobs:
wordpress:
name: WordPress (PHP ${{ matrix.php-versions }})
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
php-versions: ['8.3', '8.4', '8.5']
# Docs: https://docs.github.com/en/actions/using-containerized-services
services:
mysql:
image: mysql:latest
env:
MYSQL_ALLOW_EMPTY_PASSWORD: false
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: wordpress_test
ports:
- 3306/tcp
options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3
steps:
- name: Checkout
uses: actions/checkout@v6
# Docs: https://github.com/shivammathur/setup-php
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php-versions }}
extensions: mbstring, xml, zip, intl, mysql
coverage: none
- name: Get composer cache directory
id: composer-cache
run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
- name: Cache composer dependencies
uses: actions/cache@v5
with:
path: ${{ steps.composer-cache.outputs.dir }}
# Use composer.json for key, if composer.lock is not committed.
# key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }}
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
restore-keys: ${{ runner.os }}-composer-
- name: Install system dependencies
run: sudo apt-get update && sudo apt-get install -y subversion default-mysql-client
- name: Install Composer dependencies
run: |
if [ "${{ matrix.php-versions }}" = "8.3" ]; then
composer require --dev doctrine/instantiator:^2.0.0 --no-interaction --no-update
composer update doctrine/instantiator --with-all-dependencies --no-interaction --no-progress --prefer-dist --optimize-autoloader
else
composer install --no-interaction --no-progress --prefer-dist --optimize-autoloader
fi
- name: Install WordPress test environment
run: bash bin/install-wp-tests.sh wordpress_test root root 127.0.0.1:${{ job.services.mysql.ports['3306'] }} latest true
- name: Test with phpunit
run: vendor/bin/phpunit

View File

@@ -1,86 +0,0 @@
# GitHub Action for Yii Framework with MySQL
name: Testing Yii2 with MySQL
on: [push, pull_request]
jobs:
yii:
name: Yii2 (PHP ${{ matrix.php-versions }})
runs-on: ubuntu-latest
env:
DB_USERNAME: root
DB_PASSWORD: yii
TEST_DB_USERNAME: root
TEST_DB_PASSWORD: yii
DB_CHARSET: utf8
# Docs: https://docs.github.com/en/actions/using-containerized-services
services:
mysql:
image: mysql:latest
env:
MYSQL_ALLOW_EMPTY_PASSWORD: false
MYSQL_ROOT_PASSWORD: yii
MYSQL_DATABASE: yii
ports:
- 3306/tcp
options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3
strategy:
fail-fast: false
matrix:
php-versions: ['7.4', '8.0']
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Set Node.js 10.x
uses: actions/setup-node@v5
with:
node-version: 10.x
# Docs: https://github.com/shivammathur/setup-php
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php-versions }}
extensions: mbstring, intl, gd, imagick, zip, dom, mysql
coverage: xdebug
# Local MySQL service in GitHub hosted environments is disabled by default.
# If you are using it instead of service containers, make sure you start it.
# - name: Start mysql service
# run: sudo systemctl start mysql.service
- name: Get composer cache directory
id: composer-cache
run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
- name: Cache composer dependencies
uses: actions/cache@v3
with:
path: ${{ steps.composer-cache.outputs.dir }}
# Use composer.json for key, if composer.lock is not committed.
# key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }}
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
restore-keys: ${{ runner.os }}-composer-
- name: Install Composer dependencies
run: composer install --no-progress --prefer-dist --optimize-autoloader
- name: Prepare the application
run: |
php -r "file_exists('.env') || copy('.env.dist', '.env');"
php console/yii app/setup
npm install --development
npm run build
env:
DB_DSN: mysql:host=127.0.0.1;port=${{ job.services.mysql.ports['3306'] }};dbname=yii
TEST_DB_DSN: mysql:host=127.0.0.1;port=${{ job.services.mysql.ports['3306'] }};dbname=yii
- name: Run Tests
run: |
vendor/bin/codecept build
php tests/bin/yii app/setup --interactive=0
nohup php -S localhost:8080 > yii.log 2>&1 &
vendor/bin/codecept run
env:
DB_DSN: mysql:host=127.0.0.1;port=${{ job.services.mysql.ports['3306'] }};dbname=yii
TEST_DB_DSN: mysql:host=127.0.0.1;port=${{ job.services.mysql.ports['3306'] }};dbname=yii

View File

@@ -1,86 +0,0 @@
# GitHub Action for Yii Framework with PostgreSQL
name: Testing Yii2 with PostgreSQL
on: [push, pull_request]
jobs:
yii:
name: Yii2 (PHP ${{ matrix.php-versions }})
runs-on: ubuntu-latest
env:
DB_USERNAME: postgres
DB_PASSWORD: postgres
TEST_DB_USERNAME: postgres
TEST_DB_PASSWORD: postgres
DB_CHARSET: utf8
# Docs: https://docs.github.com/en/actions/using-containerized-services
services:
postgres:
image: postgres:latest
env:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: postgres
ports:
- 5432/tcp
options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 3
strategy:
fail-fast: false
matrix:
php-versions: ['7.4', '8.0']
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Set Node.js 10.x
uses: actions/setup-node@v5
with:
node-version: 10.x
# Docs: https://github.com/shivammathur/setup-php
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php-versions }}
extensions: mbstring, intl, gd, imagick, zip, dom, pgsql
coverage: xdebug
# Local PostgreSQL service in GitHub hosted environments is disabled by default.
# If you are using it instead of service containers, make sure you start it.
# - name: Start postgresql service
# run: sudo systemctl start postgresql.service
- name: Get composer cache directory
id: composer-cache
run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
- name: Cache composer dependencies
uses: actions/cache@v3
with:
path: ${{ steps.composer-cache.outputs.dir }}
# Use composer.json for key, if composer.lock is not committed.
# key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }}
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
restore-keys: ${{ runner.os }}-composer-
- name: Install Composer dependencies
run: composer install --no-progress --prefer-dist --optimize-autoloader
- name: Prepare the application
run: |
php -r "file_exists('.env') || copy('.env.dist', '.env');"
php console/yii app/setup
npm install --development
npm run build
env:
DB_DSN: pgsql:host=127.0.0.1;port=${{ job.services.postgres.ports['5432'] }};dbname=postgres
TEST_DB_DSN: pgsql:host=127.0.0.1;port=${{ job.services.postgres.ports['5432'] }};dbname=postgres
- name: Run Tests
run: |
vendor/bin/codecept build
php tests/bin/yii app/setup --interactive=0
nohup php -S localhost:8080 > yii.log 2>&1 &
vendor/bin/codecept run
env:
DB_DSN: pgsql:host=127.0.0.1;port=${{ job.services.postgres.ports['5432'] }};dbname=postgres
TEST_DB_DSN: pgsql:host=127.0.0.1;port=${{ job.services.postgres.ports['5432'] }};dbname=postgres

78
examples/yii3-mysql.yml Normal file
View File

@@ -0,0 +1,78 @@
# GitHub Action for Yii3 web application with MySQL
# Tested with https://github.com/yiisoft/app
name: Testing Yii3 with MySQL
on: [push, pull_request]
jobs:
yii:
name: Yii3 (PHP ${{ matrix.php-versions }})
runs-on: ubuntu-latest
env:
APP_C3: true
APP_ENV: test
APP_DEBUG: false
# Docs: https://docs.github.com/en/actions/using-containerized-services
services:
mysql:
image: mysql:8.4
env:
MYSQL_ALLOW_EMPTY_PASSWORD: false
MYSQL_ROOT_PASSWORD: password
MYSQL_DATABASE: app
ports:
- 3306/tcp
options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3
strategy:
fail-fast: false
matrix:
php-versions: ['8.4', '8.5']
# The latest yiisoft/app release resolves Symfony 8.0 packages that require PHP 8.4+.
steps:
- name: Checkout
uses: actions/checkout@v6
# Docs: https://github.com/shivammathur/setup-php
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php-versions }}
extensions: fileinfo, intl, pdo_mysql
ini-values: date.timezone='UTC', register_argc_argv=On
coverage: none
- name: Get composer cache directory
id: composer-cache
run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
- name: Cache composer dependencies
uses: actions/cache@v5
with:
path: ${{ steps.composer-cache.outputs.dir }}
# Use composer.json for key, if composer.lock is not committed.
# key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }}
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
restore-keys: ${{ runner.os }}-composer-
- name: Install Composer dependencies
run: composer install --no-progress --prefer-dist --optimize-autoloader
- name: Run migrations
run: php yii migrate:up --no-interaction
env:
DB_HOST: 127.0.0.1
DB_PORT: ${{ job.services.mysql.ports['3306'] }}
DB_NAME: app
DB_USERNAME: root
DB_PASSWORD: password
- name: Run codeception build
run: vendor/bin/codecept build
- name: Run tests with Codeception
run: vendor/bin/codecept run
env:
DB_HOST: 127.0.0.1
DB_PORT: ${{ job.services.mysql.ports['3306'] }}
DB_NAME: app
DB_USERNAME: root
DB_PASSWORD: password

View File

@@ -0,0 +1,78 @@
# GitHub Action for Yii3 web application with PostgreSQL
# Tested with https://github.com/yiisoft/app
name: Testing Yii3 with PostgreSQL
on: [push, pull_request]
jobs:
yii:
name: Yii3 (PHP ${{ matrix.php-versions }})
runs-on: ubuntu-latest
env:
APP_C3: true
APP_ENV: test
APP_DEBUG: false
# Docs: https://docs.github.com/en/actions/using-containerized-services
services:
postgres:
image: postgres:16
env:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: app
ports:
- 5432/tcp
options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 3
strategy:
fail-fast: false
matrix:
php-versions: ['8.4', '8.5']
# The latest yiisoft/app release resolves Symfony 8.0 packages that require PHP 8.4+.
steps:
- name: Checkout
uses: actions/checkout@v6
# Docs: https://github.com/shivammathur/setup-php
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php-versions }}
extensions: fileinfo, intl, pdo_pgsql
ini-values: date.timezone='UTC', register_argc_argv=On
coverage: none
- name: Get composer cache directory
id: composer-cache
run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
- name: Cache composer dependencies
uses: actions/cache@v5
with:
path: ${{ steps.composer-cache.outputs.dir }}
# Use composer.json for key, if composer.lock is not committed.
# key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }}
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
restore-keys: ${{ runner.os }}-composer-
- name: Install Composer dependencies
run: composer install --no-progress --prefer-dist --optimize-autoloader
- name: Run migrations
run: php yii migrate:up --no-interaction
env:
DB_HOST: 127.0.0.1
DB_PORT: ${{ job.services.postgres.ports['5432'] }}
DB_NAME: app
DB_USERNAME: postgres
DB_PASSWORD: postgres
- name: Run codeception build
run: vendor/bin/codecept build
- name: Run tests with Codeception
run: vendor/bin/codecept run
env:
DB_HOST: 127.0.0.1
DB_PORT: ${{ job.services.postgres.ports['5432'] }}
DB_NAME: app
DB_USERNAME: postgres
DB_PASSWORD: postgres

53
examples/yii3.yml Normal file
View File

@@ -0,0 +1,53 @@
# GitHub Action for Yii3 web application
# Tested with https://github.com/yiisoft/app
name: Testing Yii3
on: [push, pull_request]
jobs:
yii:
name: Yii3 (PHP ${{ matrix.php-versions }} on ${{ matrix.operating-system }})
runs-on: ${{ matrix.operating-system }}
env:
APP_C3: true
APP_ENV: test
APP_DEBUG: false
strategy:
fail-fast: false
matrix:
operating-system: [ubuntu-latest, windows-latest]
php-versions: ['8.4', '8.5']
# The latest yiisoft/app release resolves Symfony 8.0 packages that require PHP 8.4+.
steps:
- name: Checkout
uses: actions/checkout@v6
# Docs: https://github.com/shivammathur/setup-php
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php-versions }}
extensions: fileinfo, intl
ini-values: date.timezone='UTC', register_argc_argv=On
coverage: none
- name: Get composer cache directory
id: composer-cache
shell: bash
run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
- name: Cache composer dependencies
uses: actions/cache@v5
with:
path: ${{ steps.composer-cache.outputs.dir }}
# Use composer.json for key, if composer.lock is not committed.
# key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }}
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
restore-keys: ${{ runner.os }}-composer-
- name: Install Composer dependencies
run: composer install --no-progress --prefer-dist --optimize-autoloader
- name: Run codeception build
run: vendor/bin/codecept build
- name: Run tests with Codeception
run: vendor/bin/codecept run

4596
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{ {
"name": "setup-php", "name": "setup-php",
"version": "2.36.0", "version": "2.37.1",
"private": false, "private": false,
"description": "Setup PHP for use with GitHub Actions", "description": "Setup PHP for use with GitHub Actions",
"main": "lib/install.js", "main": "lib/install.js",
@@ -34,36 +34,36 @@
"author": "shivammathur", "author": "shivammathur",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@actions/core": "^1.11.1", "@actions/exec": "^2.0.0",
"@actions/exec": "^1.1.1",
"@actions/io": "^2.0.0",
"compare-versions": "^6.1.1" "compare-versions": "^6.1.1"
}, },
"devDependencies": { "devDependencies": {
"@eslint/compat": "^2.0.0", "@eslint/compat": "^2.1.0",
"@eslint/js": "9.39.1", "@eslint/js": "^10.0.1",
"@types/jest": "^30.0.0", "@types/jest": "^30.0.0",
"@types/node": "^24.10.1", "@types/node": "^25.7.0",
"@typescript-eslint/eslint-plugin": "^8.48.0", "@typescript-eslint/eslint-plugin": "^8.59.3",
"@typescript-eslint/parser": "^8.48.0", "@typescript-eslint/parser": "^8.59.3",
"@vercel/ncc": "^0.38.4", "@vercel/ncc": "^0.38.4",
"eslint": "9.39.1", "eslint": "^10.3.0",
"eslint-config-prettier": "^10.1.8", "eslint-config-prettier": "^10.1.8",
"eslint-plugin-import": "^2.32.0", "eslint-import-resolver-typescript": "^4.4.4",
"eslint-plugin-jest": "^29.2.1", "eslint-plugin-import-x": "^4.16.2",
"eslint-plugin-prettier": "^5.5.4", "eslint-plugin-jest": "^29.15.2",
"globals": "^16.5.0", "eslint-plugin-prettier": "^5.5.5",
"jest": "^30.2.0", "globals": "^17.6.0",
"jest-circus": "^30.2.0", "jest": "^30.4.2",
"nock": "^14.0.10", "jest-circus": "^30.4.2",
"prettier": "^3.6.2", "nock": "^14.0.15",
"prettier": "^3.8.3",
"simple-git-hooks": "^2.13.1", "simple-git-hooks": "^2.13.1",
"ts-jest": "^29.4.5", "ts-jest": "^29.4.9",
"typescript": "^5.9.3" "typescript": "^5.9.3"
}, },
"overrides": { "overrides": {
"test-exclude": "^7.0.1", "test-exclude": "^7.0.1",
"glob": "^11.1.0" "glob": "^13.0.6",
"minimatch": "^10.2.1"
}, },
"bugs": { "bugs": {
"url": "https://github.com/shivammathur/setup-php/issues" "url": "https://github.com/shivammathur/setup-php/issues"

View File

@@ -16,7 +16,7 @@ export async function addINIValuesUnix(
}); });
return ( return (
'echo "' + 'echo "' +
ini_values.join('\n') + ini_values.map(v => utils.escapeForShell(v, 'linux')).join('\n') +
'" | sudo tee -a "${pecl_file:-${ini_file[@]}}" >/dev/null 2>&1' + '" | sudo tee -a "${pecl_file:-${ini_file[@]}}" >/dev/null 2>&1' +
script script
); );
@@ -37,7 +37,10 @@ export async function addINIValuesWindows(
(await utils.addLog('$tick', line, 'Added to php.ini', 'win32')) + '\n'; (await utils.addLog('$tick', line, 'Added to php.ini', 'win32')) + '\n';
}); });
return ( return (
'Add-Content "$php_dir\\php.ini" "' + ini_values.join('\n') + '"' + script 'Add-Content "$php_dir\\php.ini" "' +
ini_values.map(v => utils.escapeForShell(v, 'win32')).join('\n') +
'"' +
script
); );
} }

View File

@@ -1,29 +1,40 @@
amqp=amqp amqp=amqp
apcu=apcu apcu=apcu
ast=ast ast=ast
brotli=brotli
couchbase=couchbase couchbase=couchbase
ds=ds ds=ds
event=event event=event
excimer=excimer
expect=expect expect=expect
gearman=gearman gearman=gearman
gmagick=gmagick
gnupg=gnupg gnupg=gnupg
grpc=grpc grpc=grpc
igbinary=igbinary igbinary=igbinary
imagick=imagick imagick=imagick
imap=imap imap=imap
interbase=interbase
lua=lua lua=lua
mailparse=mailparse mailparse=mailparse
maxminddb=maxminddb
mcrypt=mcrypt mcrypt=mcrypt
memcache=memcache memcache=memcache
memcached=memcached memcached=memcached
mongodb=mongodb mongodb=mongodb
mongodb1=mongodb1
msgpack=msgpack msgpack=msgpack
newrelic=newrelic
oauth=oauth
opentelemetry=opentelemetry
pcov=pcov pcov=pcov
pdo_firebird=pdo_firebird
pdo_sqlsrv=pdo_sqlsrv pdo_sqlsrv=pdo_sqlsrv
pecl_http=http pecl_http=http
phalcon3=phalcon phalcon3=phalcon
phalcon4=phalcon phalcon4=phalcon
phalcon5=phalcon phalcon5=phalcon
pinba=pinba
propro=propro propro=propro
protobuf=protobuf protobuf=protobuf
psr=psr psr=psr
@@ -31,16 +42,24 @@ raphf=raphf
rdkafka=rdkafka rdkafka=rdkafka
phpredis=redis phpredis=redis
redis=redis redis=redis
seaslog=seaslog
scalar_objects=scalar_objects
snmp=snmp snmp=snmp
sqlsrv=sqlsrv sqlsrv=sqlsrv
spx=spx
ssh2=ssh2 ssh2=ssh2
swoole=swoole swoole=swoole
swow=swow
uopz=uopz
uploadprogress=uploadprogress
uuid=uuid uuid=uuid
v8js=v8js v8js=v8js
vips=vips vips=vips
vld=vld vld=vld
xdebug=xdebug xdebug=xdebug
xdebug2=xdebug xdebug2=xdebug
xhprof=xhprof
xlswriter=xlswriter xlswriter=xlswriter
yaml=yaml yaml=yaml
zmq=zmq zmq=zmq
zstd=zstd

View File

@@ -0,0 +1,3 @@
1.0.0-0 1.10.28
2.0.0-0 2.2.28
2.3.0-0 2.9.8

View File

@@ -0,0 +1 @@
Composer %s has a known GitHub token parsing bug that exposes GitHub tokens in the error output. So, GitHub authentication has not been configured for this Composer version. Please update to the latest version of Composer. See: https://github.com/composer/composer/security/advisories/GHSA-f9f8-rm49-7jv2

View File

@@ -21,4 +21,7 @@
23.10,mantic 23.10,mantic
24.04,noble 24.04,noble
24.10,oracular 24.10,oracular
25.10,plucky 25.05,plucky
25.10,questing
26.04,resolute
26.10,stonking
1 8 jessie
21 23.10 mantic
22 24.04 noble
23 24.10 oracular
24 25.10 25.05 plucky
25 25.10 questing
26 26.04 resolute
27 26.10 stonking

112
src/core.ts Normal file
View File

@@ -0,0 +1,112 @@
import {EOL} from 'os';
/**
* Commands
*
* Command Format:
* ::name key=value,key=value::message
*
* @see https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions
*/
interface CommandProperties {
[key: string]: string;
}
/**
* Sanitizes the message for use in a workflow command.
* @param message
*/
function toCommandValue(message: string | Error): string {
if (message instanceof Error) {
return message.toString();
}
return message;
}
/**
* Escapes data for safe use in workflow command messages.
* @param s
*/
function escapeData(s: string | Error): string {
return toCommandValue(s)
.replace(/%/g, '%25')
.replace(/\r/g, '%0D')
.replace(/\n/g, '%0A');
}
/**
* Escapes property values for safe use in workflow command properties.
* @param s
*/
function escapeProperty(s: string): string {
return s
.replace(/%/g, '%25')
.replace(/\r/g, '%0D')
.replace(/\n/g, '%0A')
.replace(/:/g, '%3A')
.replace(/,/g, '%2C');
}
/**
* Issues a command to the GitHub Actions runner.
*
* @param command - The command name to issue
* @param properties - Additional properties for the command (key-value pairs)
* @param message - The message to include with the command
*/
export function issueCommand(
command: string,
properties: CommandProperties,
message: string | Error
): void {
let cmdStr = `::${command}`;
if (properties && Object.keys(properties).length > 0) {
cmdStr += ' ';
const props = Object.entries(properties)
.filter(([, val]) => val)
.map(([key, val]) => `${key}=${escapeProperty(val)}`)
.join(',');
cmdStr += props;
}
cmdStr += `::${escapeData(message)}`;
process.stdout.write(cmdStr + EOL);
}
/**
* Adds an error issue.
* @param message - error issue message
*/
export function error(message: string | Error): void {
issueCommand('error', {}, message);
}
/**
* Sets the action status to failed.
* When the action exits it will be with an exit code of 1.
* @param message - add error issue message
*/
export function setFailed(message: string | Error): void {
process.exitCode = 1;
error(message);
}
/**
* Gets the value of an input.
* The value is trimmed.
* Returns an empty string if the value is not defined.
*
* @param name - name of the input to get
* @param required - whether the input is required
* @returns string
*/
export function getInput(name: string, required = false): string {
const val: string =
process.env[`INPUT_${name.replace(/ /g, '_').toUpperCase()}`] || '';
if (required && !val) {
throw new Error(`Input required and not supplied: ${name}`);
}
return val.trim();
}

View File

@@ -35,9 +35,9 @@ export async function addExtensionDarwin(
// match 5.3blackfire...8.5blackfire // match 5.3blackfire...8.5blackfire
// match 5.3blackfire-(semver)...8.5blackfire-(semver) // match 5.3blackfire-(semver)...8.5blackfire-(semver)
// match couchbase, event, geos, ibm_db2, pdo_ibm, pdo_oci, oci8, http, pecl_http // match couchbase, event, geos, ibm_db2, pdo_ibm, pdo_oci, oci8, http, pecl_http
// match 5.3ioncube...8.4ioncube // match 5.3ioncube...8.5ioncube
// match 7.0phalcon3...7.3phalcon3, 7.2phalcon4...7.4phalcon4, and 7.4phalcon5...8.4phalcon5 // match 7.0phalcon3...7.3phalcon3, 7.2phalcon4...7.4phalcon4, and 7.4phalcon5...8.4phalcon5
// match 7.0zephir_parser...8.4zephir_parser // match 7.0zephir_parser...8.5zephir_parser
case /^(7\.4|8\.[0-5])relay(-v?\d+\.\d+\.\d+|-nightly)?$/.test( case /^(7\.4|8\.[0-5])relay(-v?\d+\.\d+\.\d+|-nightly)?$/.test(
version_extension version_extension
): ):
@@ -47,12 +47,12 @@ export async function addExtensionDarwin(
case /^couchbase|^event|^gearman$|^geos$|^ibm_db2$|^pdo_ibm$|^pdo_oci$|^oci8$|^(pecl_)?http|^pdo_firebird$/.test( case /^couchbase|^event|^gearman$|^geos$|^ibm_db2$|^pdo_ibm$|^pdo_oci$|^oci8$|^(pecl_)?http|^pdo_firebird$/.test(
extension extension
): ):
case /^(5\.[3-6]|7\.[0-4]|8\.[0-4])ioncube$/.test(version_extension): case /^(5\.[3-6]|7\.[0-4]|8\.[0-5])ioncube$/.test(version_extension):
case /(5\.6|7\.[0-3])phalcon3|7\.[2-4]phalcon4|(7\.4|8\.[0-4])phalcon5?/.test( case /(5\.6|7\.[0-3])phalcon3|7\.[2-4]phalcon4|(7\.4|8\.[0-4])phalcon5?/.test(
version_extension version_extension
): ):
case /(?<!5\.[3-6])(pdo_)?sqlsrv$/.test(version_extension): case /(?<!5\.[3-6])(pdo_)?sqlsrv$/.test(version_extension):
case /^(7\.[0-4]|8\.[0-4])zephir_parser(-v?\d+\.\d+\.\d+)?$/.test( case /^(7\.[0-4]|8\.[0-5])zephir_parser(-v?\d+\.\d+\.\d+)?$/.test(
version_extension version_extension
): ):
add_script += await utils.customPackage( add_script += await utils.customPackage(
@@ -85,13 +85,14 @@ export async function addExtensionDarwin(
add_script += await utils.getUnsupportedLog('pcov', version, 'darwin'); add_script += await utils.getUnsupportedLog('pcov', version, 'darwin');
return; return;
// match brew extensions // match brew extensions
case /(?<!5\.[3-5])(amqp|apcu|expect|gnupg|grpc|igbinary|imagick|imap|mailparse|mcrypt|memcache|memcached|mongodb|msgpack|protobuf|psr|raphf|rdkafka|redis|snmp|ssh2|swoole|uuid|vld|xdebug|xdebug2|yaml|zmq)/.test( case /(?<!5\.[3-5])(amqp|apcu|brotli|excimer|expect|gmagick|gnupg|grpc|igbinary|imagick|imap|interbase|mailparse|maxminddb|mcrypt|memcache|memcached|mongodb|mongodb1|msgpack|newrelic|oauth|opentelemetry|pdo_firebird|pinba|protobuf|psr|raphf|rdkafka|redis|scalar_objects|seaslog|snmp|spx|ssh2|swoole|uopz|uploadprogress|uuid|vld|xdebug|xdebug2|xhprof|yaml|zmq|zstd)/.test(
version_extension version_extension
): ):
case /(?<!5\.[3-6])(ds|v8js)/.test(version_extension): case /(?<!5\.[3-6])(ds|v8js)/.test(version_extension):
case /(5\.6|7\.[0-4])(propro|lua)/.test(version_extension): case /(5\.6|7\.[0-4])(propro|lua)/.test(version_extension):
case /(?<!5\.[3-6]|7\.0)pcov/.test(version_extension): case /(?<!5\.[3-6]|7\.0)pcov/.test(version_extension):
case /(?<!5\.[3-6])(ast|vips|xlswriter)/.test(version_extension): case /(?<!5\.[3-6])(ast|vips|xlswriter)/.test(version_extension):
case /^(8\.[0-5])swow$/.test(version_extension):
add_script += await utils.joins( add_script += await utils.joins(
'\nadd_brew_extension', '\nadd_brew_extension',
ext_name, ext_name,
@@ -139,23 +140,23 @@ export async function addExtensionWindows(
// match 5.3blackfire...8.5blackfire // match 5.3blackfire...8.5blackfire
// match 5.3blackfire-(semver)...8.5blackfire-(semver) // match 5.3blackfire-(semver)...8.5blackfire-(semver)
// match ibm_db2, pdo_ibm, pdo_oci and oci8 // match ibm_db2, pdo_ibm, pdo_oci and oci8
// match 5.3ioncube...8.4ioncube // match 5.3ioncube...8.5ioncube
// match 7.0phalcon3...7.3phalcon3, 7.2phalcon4...7.4phalcon4, and 7.4phalcon5...8.4phalcon5 // match 7.0phalcon3...7.3phalcon3, 7.2phalcon4...7.4phalcon4, and 7.4phalcon5...8.4phalcon5
// match 7.1pecl_http...8.1pecl_http and 7.1http...8.1http // match 7.1pecl_http...8.5pecl_http and 7.1http...8.5http
// match 7.0zephir_parser...8.4zephir_parser // match 7.0zephir_parser...8.5zephir_parser
case /^(5\.[3-6]|7\.[0-4]|8\.[0-5])blackfire(-\d+\.\d+\.\d+)?$/.test( case /^(5\.[3-6]|7\.[0-4]|8\.[0-5])blackfire(-\d+\.\d+\.\d+)?$/.test(
version_extension version_extension
): ):
case /^ibm_db2$|^pdo_ibm$|^pdo_oci$|^oci8$|^pdo_firebird$/.test( case /^ibm_db2$|^pdo_ibm$|^pdo_oci$|^oci8$|^pdo_firebird$/.test(
extension extension
): ):
case /^(5\.[3-6]|7\.[0-4]|8\.[0-4])ioncube$/.test(version_extension): case /^(5\.[3-6]|7\.[0-4]|8\.[0-5])ioncube$/.test(version_extension):
case /^7\.[0-3]phalcon3$|^7\.[2-4]phalcon4$|^(7\.4|8\.[0-4])phalcon5?$/.test( case /^7\.[0-3]phalcon3$|^7\.[2-4]phalcon4$|^(7\.4|8\.[0-4])phalcon5?$/.test(
version_extension version_extension
): ):
case /^(7\.[1-4]|8\.1)(pecl_)?http/.test(version_extension): case /^(7\.[1-4]|8\.[0-5])(pecl_)?http/.test(version_extension):
case /(?<!5\.[3-6])(pdo_)?sqlsrv$/.test(version_extension): case /(?<!5\.[3-6])(pdo_)?sqlsrv$/.test(version_extension):
case /^(7\.[0-4]|8\.[0-4])zephir_parser(-v?\d+\.\d+\.\d+)?$/.test( case /^(7\.[0-4]|8\.[0-5])zephir_parser(-v?\d+\.\d+\.\d+)?$/.test(
version_extension version_extension
): ):
add_script += await utils.customPackage( add_script += await utils.customPackage(
@@ -272,9 +273,9 @@ export async function addExtensionLinux(
// match 5.3blackfire-(semver)...8.5blackfire-(semver) // match 5.3blackfire-(semver)...8.5blackfire-(semver)
// match 5.3pdo_cubrid...7.2php_cubrid, 5.3cubrid...7.4cubrid // match 5.3pdo_cubrid...7.2php_cubrid, 5.3cubrid...7.4cubrid
// match couchbase, geos, ibm_db2, pdo_ibm, pdo_oci, oci8, http, pecl_http // match couchbase, geos, ibm_db2, pdo_ibm, pdo_oci, oci8, http, pecl_http
// match 5.3ioncube...8.4ioncube // match 5.3ioncube...8.5ioncube
// match 7.0phalcon3...7.3phalcon3, 7.2phalcon4...7.4phalcon4, 7.4phalcon5...8.4phalcon5 // match 7.0phalcon3...7.3phalcon3, 7.2phalcon4...7.4phalcon4, 7.4phalcon5...8.4phalcon5
// match 7.0zephir_parser...8.4zephir_parser // match 7.0zephir_parser...8.5zephir_parser
case /^(7\.4|8\.[0-5])relay(-v?\d+\.\d+\.\d+|-nightly)?$/.test( case /^(7\.4|8\.[0-5])relay(-v?\d+\.\d+\.\d+|-nightly)?$/.test(
version_extension version_extension
): ):
@@ -288,12 +289,12 @@ export async function addExtensionLinux(
extension extension
): ):
case /(?<!5\.[3-5])intl-\d+\.\d+$/.test(version_extension): case /(?<!5\.[3-5])intl-\d+\.\d+$/.test(version_extension):
case /^(5\.[3-6]|7\.[0-4]|8\.[0-4])ioncube$/.test(version_extension): case /^(5\.[3-6]|7\.[0-4]|8\.[0-5])ioncube$/.test(version_extension):
case /^7\.[0-3]phalcon3$|^7\.[2-4]phalcon4$|^(7\.4|8\.[0-4])phalcon5?$/.test( case /^7\.[0-3]phalcon3$|^7\.[2-4]phalcon4$|^(7\.4|8\.[0-4])phalcon5?$/.test(
version_extension version_extension
): ):
case /(?<!5\.[3-6])(pdo_)?sqlsrv$/.test(version_extension): case /(?<!5\.[3-6])(pdo_)?sqlsrv$/.test(version_extension):
case /^(7\.[0-4]|8\.[0-4])zephir_parser(-v?\d+\.\d+\.\d+)?$/.test( case /^(7\.[0-4]|8\.[0-5])zephir_parser(-v?\d+\.\d+\.\d+)?$/.test(
version_extension version_extension
): ):
add_script += await utils.customPackage( add_script += await utils.customPackage(

View File

@@ -1,9 +1,10 @@
import {IncomingMessage, OutgoingHttpHeaders} from 'http'; /**
import * as https from 'https'; * Redirect status codes set for O(1) lookup
import * as url from 'url'; */
const REDIRECT_CODES = new Set([301, 302, 303, 307, 308]);
/** /**
* Function to fetch a URL * Function to fetch a URL using native fetch API (Node 24+)
* *
* @param input_url * @param input_url
* @param auth_token * @param auth_token
@@ -14,43 +15,28 @@ export async function fetch(
auth_token?: string, auth_token?: string,
redirect_count = 5 redirect_count = 5
): Promise<Record<string, string>> { ): Promise<Record<string, string>> {
const fetch_promise: Promise<Record<string, string>> = new Promise( const headers: Record<string, string> = {
resolve => { 'User-Agent': `Mozilla/5.0 (${process.platform} ${process.arch}) setup-php`
const url_object: url.UrlObject = new url.URL(input_url); };
const headers: OutgoingHttpHeaders = { if (auth_token) {
'User-Agent': `Mozilla/5.0 (${process.platform} ${process.arch}) setup-php` headers['Authorization'] = 'Bearer ' + auth_token;
}; }
if (auth_token) {
headers.authorization = 'Bearer ' + auth_token; try {
} const response = await globalThis.fetch(input_url, {
const options: https.RequestOptions = { headers,
hostname: url_object.hostname, redirect: redirect_count > 0 ? 'follow' : 'manual'
path: url_object.pathname, });
headers: headers,
agent: new https.Agent({keepAlive: false}) if (response.ok) {
}; const data = await response.text();
const req = https.get(options, (res: IncomingMessage) => { return {data};
if (res.statusCode === 200) { } else if (REDIRECT_CODES.has(response.status) && redirect_count <= 0) {
let body = ''; return {error: `${response.status}: Redirect error`};
res.setEncoding('utf8'); } else {
res.on('data', chunk => (body += chunk)); return {error: `${response.status}: ${response.statusText}`};
res.on('end', () => resolve({data: `${body}`}));
} else if (
[301, 302, 303, 307, 308].includes(res.statusCode as number)
) {
if (redirect_count > 0 && res.headers.location) {
fetch(res.headers.location, auth_token, redirect_count--).then(
resolve
);
} else {
resolve({error: `${res.statusCode}: Redirect error`});
}
} else {
resolve({error: `${res.statusCode}: ${res.statusMessage}`});
}
});
req.end();
} }
); } catch (error) {
return await fetch_promise; return {error: `Fetch error: ${(error as Error).message}`};
}
} }

View File

@@ -1,8 +1,8 @@
import path from 'path'; import path from 'path';
import fs from 'fs'; import fs from 'fs';
import {exec} from '@actions/exec'; import {exec} from '@actions/exec';
import * as core from '@actions/core';
import * as config from './config'; import * as config from './config';
import * as core from './core';
import * as coverage from './coverage'; import * as coverage from './coverage';
import * as extensions from './extensions'; import * as extensions from './extensions';
import * as tools from './tools'; import * as tools from './tools';
@@ -18,7 +18,10 @@ export async function getScript(os: string): Promise<string> {
const filename = os + (await utils.scriptExtension(os)); const filename = os + (await utils.scriptExtension(os));
const script_path = path.join(__dirname, '../src/scripts', filename); const script_path = path.join(__dirname, '../src/scripts', filename);
const run_path = script_path.replace(os, 'run'); const run_path = script_path.replace(os, 'run');
const extension_csv: string = await utils.getInput('extensions', false); const extension_csv: string = utils.sanitizeShellInput(
await utils.getInput('extensions', false),
true
);
const ini_values_csv: string = await utils.getInput('ini-values', false); const ini_values_csv: string = await utils.getInput('ini-values', false);
const coverage_driver: string = await utils.getInput('coverage', false); const coverage_driver: string = await utils.getInput('coverage', false);
const tools_csv: string = await utils.getInput('tools', false); const tools_csv: string = await utils.getInput('tools', false);
@@ -28,7 +31,7 @@ export async function getScript(os: string): Promise<string> {
const ini_file: string = await utils.parseIniFile( const ini_file: string = await utils.parseIniFile(
await utils.getInput('ini-file', false) await utils.getInput('ini-file', false)
); );
let script = await utils.joins('.', script_path, version, ini_file); let script = await utils.joins('.', script_path, `'${version}'`, ini_file);
if (extension_csv) { if (extension_csv) {
script += await extensions.addExtension(extension_csv, version, os); script += await extensions.addExtension(extension_csv, version, os);
} }

View File

@@ -15,7 +15,9 @@ handle_dependency_extensions() {
brew_opts=(-sf) brew_opts=(-sf)
patch_abstract_file >/dev/null 2>&1 patch_abstract_file >/dev/null 2>&1
for dependency_extension in "${dependency_extensions[@]}"; do for dependency_extension in "${dependency_extensions[@]}"; do
brew install "${brew_opts[@]}" "$ext_tap/$dependency_extension@$version" >/dev/null 2>&1 && copy_brew_extensions "$dependency_extension" safe_brew install --skip-link "${brew_opts[@]}" "$ext_tap/$dependency_extension@$version" >/dev/null 2>&1 &&
brew link --overwrite --force "$dependency_extension@$version" >/dev/null 2>&1 &&
copy_brew_extensions "$dependency_extension"
done done
fi fi
} }
@@ -83,7 +85,11 @@ add_brew_extension() {
formula="$(get_renamed_formula "$formula")" formula="$(get_renamed_formula "$formula")"
update_dependencies >/dev/null 2>&1 update_dependencies >/dev/null 2>&1
handle_dependency_extensions "$formula" "$extension" >/dev/null 2>&1 handle_dependency_extensions "$formula" "$extension" >/dev/null 2>&1
(brew install "${brew_opts[@]}" "$ext_tap/$formula@$version" >/dev/null 2>&1 && copy_brew_extensions "$formula") || pecl_install "$extension" >/dev/null 2>&1 (
safe_brew install --skip-link "${brew_opts[@]}" "$ext_tap/$formula@$version" >/dev/null 2>&1 &&
brew link --overwrite --force "$formula@$version" >/dev/null 2>&1 &&
copy_brew_extensions "$formula"
) || pecl_install "$extension" >/dev/null 2>&1
add_extension_log "$extension" "Installed and enabled" add_extension_log "$extension" "Installed and enabled"
fi fi
} }
@@ -181,14 +187,14 @@ add_php() {
fi fi
if [[ "$existing_version" != "false" && -z "$suffix" ]]; then if [[ "$existing_version" != "false" && -z "$suffix" ]]; then
if [ "$action" = "upgrade" ]; then if [ "$action" = "upgrade" ]; then
brew install --only-dependencies "$php_formula" safe_brew install --only-dependencies "$php_formula"
brew upgrade -f --overwrite "$php_formula" safe_brew upgrade -f --overwrite "$php_formula"
else else
brew unlink "$php_keg" brew unlink "$php_keg"
fi fi
else else
brew install --only-dependencies "$php_formula" safe_brew install --only-dependencies "$php_formula"
brew install -f --overwrite "$php_formula" 2>/dev/null || brew upgrade -f --overwrite "$php_formula" safe_brew install --skip-link -f --overwrite "$php_formula" 2>/dev/null || safe_brew upgrade -f --overwrite "$php_formula"
fi fi
brew link --force --overwrite "$php_keg" || (sudo chown -R "$(id -un)":"$(id -gn)" "$brew_prefix" && brew link --force --overwrite "$php_keg") brew link --force --overwrite "$php_keg" || (sudo chown -R "$(id -un)":"$(id -gn)" "$brew_prefix" && brew link --force --overwrite "$php_keg")
} }

View File

@@ -79,9 +79,20 @@ add_couchbase() {
add_extension_log "couchbase" "Installed and enabled" add_extension_log "couchbase" "Installed and enabled"
fi fi
else else
if [ -e "${ext_dir:?}"/libcouchbase_php_core.dylib ]; then if [ -e "${ext_dir:?}/couchbase.so" ]; then
sudo cp "${ext_dir:?}"/libcouchbase_php_core.dylib "${brew_prefix:?}"/lib couchbase_rpath="$(otool -l "${ext_dir:?}/couchbase.so" 2>/dev/null | awk '$1 == "path" && $2 ~ /\/couchbase@'"${version:?}"'\// {print $2; exit}')"
couchbase_rpath="${couchbase_rpath/@loader_path/${ext_dir:?}}"
otool -L "${ext_dir:?}/couchbase.so" 2>/dev/null |
awk -v rpath="$couchbase_rpath" '/libcouchbase_php.*\.dylib/ {if ($1 ~ /^@rpath\// && rpath != "") {sub(/^@rpath/, rpath, $1)}; print $1}' |
while read -r dylib; do
dylib="${dylib/@loader_path/${ext_dir:?}}"
[ -e "${ext_dir:?}/$(basename "$dylib")" ] || continue
sudo mkdir -p "$(dirname "$dylib")"
sudo cp "${ext_dir:?}/$(basename "$dylib")" "$dylib"
done
fi fi
add_brew_extension couchbase extension add_brew_extension couchbase extension
find "${brew_prefix:?}/lib" "${brew_prefix:?}/opt/couchbase@${version:?}" "${brew_prefix:?}/Cellar/couchbase@${version:?}" \
-name 'libcouchbase_php*.dylib' -exec sudo cp {} "${ext_dir:?}" \; >/dev/null 2>&1
fi fi
} }

View File

@@ -1,17 +1,3 @@
add_firebird_client_darwin() {
firebird_tag='v5.0.0'
arch_name='x64'
arch="$(uname -m)"
[[ "$arch" = "arm64" || "$arch" = "aarch64" ]] && arch_name='arm64'
pkg_name=$(get -s -n "" https://api.github.com/repos/FirebirdSQL/firebird/releases/tags/"$firebird_tag" | grep -Eo "Firebird-.*.-$arch_name.pkg" | head -n 1)
[ -z "$pkg_name" ] && pkg_name=$(get -s -n "" https://github.com/FirebirdSQL/firebird/releases/expanded_assets/"$firebird_tag" | grep -Eo "Firebird-.*.-$arch_name.pkg" | head -n 1)
get -q -e "/tmp/firebird.pkg" https://github.com/FirebirdSQL/firebird/releases/download/"$firebird_tag"/"$pkg_name"
sudo installer -pkg /tmp/firebird.pkg -target /
sudo mkdir -p /opt/firebird/include /opt/firebird/lib
sudo cp -a /Library/Frameworks/Firebird.framework/Headers/* /opt/firebird/include/
sudo find /Library/Frameworks/Firebird.framework -name '*.dylib' -exec cp "{}" /opt/firebird/lib \;
}
add_firebird_helper() { add_firebird_helper() {
firebird_dir=$1 firebird_dir=$1
tag="$(php_src_tag)" tag="$(php_src_tag)"
@@ -23,22 +9,19 @@ add_firebird_helper() {
} }
add_firebird() { add_firebird() {
if [ "$(uname -s )" = "Darwin" ]; then
add_firebird_client_darwin >/dev/null 2>&1
fi
enable_extension pdo_firebird extension enable_extension pdo_firebird extension
status="Enabled" if check_extension pdo_firebird; then
if ! check_extension pdo_firebird; then add_log "${tick:?}" pdo_firebird Enabled
status="Installed and enabled" else
if [ "$(uname -s)" = "Linux" ]; then if [ "$(uname -s)" = "Linux" ]; then
if [[ "${version:?}" =~ 5.3|${nightly_versions:?} ]]; then if [[ "${version:?}" =~ 5.3|${php_builder_versions:?} ]]; then
add_firebird_helper /usr >/dev/null 2>&1 add_firebird_helper /usr >/dev/null 2>&1
else else
add_pdo_extension firebird >/dev/null 2>&1 add_pdo_extension firebird >/dev/null 2>&1
fi fi
else else
add_firebird_helper /opt/firebird >/dev/null 2>&1 add_brew_extension pdo_firebird extension >/dev/null 2>&1
fi fi
add_extension_log pdo_firebird "Installed and enabled"
fi fi
add_extension_log pdo_firebird "$status"
} }

View File

@@ -1,9 +1,9 @@
# Helper function to add gearman extension. # Helper function to add gearman extension.
add_gearman_helper() { add_gearman_helper() {
install_packages libgearman-dev
enable_extension gearman extension enable_extension gearman extension
if ! check_extension gearman; then if ! check_extension gearman; then
status="Installed and enabled" status="Installed and enabled"
install_packages libgearman-dev
if [[ "${version:?}" =~ 5.[3-6] ]]; then if [[ "${version:?}" =~ 5.[3-6] ]]; then
pecl_install gearman-1.1.2 pecl_install gearman-1.1.2
elif [[ "${version:?}" =~ 7.0 ]]; then elif [[ "${version:?}" =~ 7.0 ]]; then

View File

@@ -10,13 +10,15 @@ Function Get-ICUUrl() {
[ValidateNotNull()] [ValidateNotNull()]
$vs_version $vs_version
) )
$trunk = "https://windows.php.net" $trunk = "https://downloads.php.net"
$urls=@("${trunk}/downloads/php-sdk/deps/${vs_version}/${arch}", "${trunk}/downloads/php-sdk/deps/archives/${vs_version}/${arch}") $urls=@("${trunk}/~windows/php-sdk/deps/${vs_version}/${arch}/", "${trunk}/~windows/php-sdk/deps/archives/${vs_version}/${arch}/")
foreach ($url in $urls) { foreach ($url in $urls) {
$web_content = Get-File -Url $url try {
$web_content = Get-File -Url $url 2>$null
} catch { continue }
foreach ($link in $web_content.Links) { foreach ($link in $web_content.Links) {
if ($link -match "/.*ICU-${icu_version}.*/") { if ($link.href -match ".*ICU-${icu_version}.*") {
return $trunk + $link.HREF return $url + $link.HREF
} }
} }
} }

View File

@@ -19,26 +19,6 @@ Function Get-PhalconReleaseAssetUrl() {
$match = (Get-File -Url "$github/$releases/expanded_assets/v$Semver").Links.href | Select-String -Pattern "(phalcon_${arch}_.*_php${version}_${extension_version}.*[0-9]${nts}.zip)" $match = (Get-File -Url "$github/$releases/expanded_assets/v$Semver").Links.href | Select-String -Pattern "(phalcon_${arch}_.*_php${version}_${extension_version}.*[0-9]${nts}.zip)"
} catch { } } catch { }
} }
} else {
$nts = if (!$installed.ThreadSafe) { "-nts" } else { "-ts" }
try {
$match = (Invoke-RestMethod -Uri "$domain/$releases/tags/v$Semver").assets | Select-String -Pattern "browser_download_url=.*(php_phalcon-php${version}${nts}-windows.*-x64.zip)"
} catch { }
if($null -eq $match) {
try {
$match = (Get-File -Url "$github/$releases/expanded_assets/v$Semver").Links.href | Select-String -Pattern "(php_phalcon-php${version}${nts}-windows.*-x64.zip)"
} catch { }
}
if($null -eq $match) {
try {
$match = (Invoke-RestMethod -Uri "$domain/$releases/tags/v$Semver").assets | Select-String -Pattern "browser_download_url=.*(phalcon-php${version}${nts}-windows.*-x64.zip)"
} catch { }
}
if($null -eq $match) {
try {
$match = (Get-File -Url "$github/$releases/expanded_assets/v$Semver").Links.href | Select-String -Pattern "(phalcon-php${version}${nts}-windows.*-x64.zip)"
} catch { }
}
} }
if($NULL -ne $match) { if($NULL -ne $match) {
return "$github/$releases/download/v$Semver/$($match.Matches[0].Groups[1].Value)" return "$github/$releases/download/v$Semver/$($match.Matches[0].Groups[1].Value)"
@@ -73,6 +53,8 @@ Function Get-PhalconSemver() {
return '4.1.0' return '4.1.0'
} elseif (($extension_version -eq '5') -and ($version -eq '7.4')) { } elseif (($extension_version -eq '5') -and ($version -eq '7.4')) {
return '5.4.0' return '5.4.0'
} elseif (($extension_version -eq '5') -and ($version -eq '8.0')) {
return '5.10.0'
} }
return Get-PeclPackageVersion phalcon $extension_version stable stable | Select-Object -First 1 return Get-PeclPackageVersion phalcon $extension_version stable stable | Select-Object -First 1
} }
@@ -80,7 +62,7 @@ Function Get-PhalconSemver() {
# Function to install phalcon # Function to install phalcon
Function Add-PhalconHelper() { Function Add-PhalconHelper() {
$semver = Get-PhalconSemver $semver = Get-PhalconSemver
if ($extension_version -eq '3') { if ($extension_version -match '[3-4]') {
Add-PhalconFromGitHub $semver Add-PhalconFromGitHub $semver
} else { } else {
Add-Extension -Extension phalcon -Stability stable -Extension_version $semver Add-Extension -Extension phalcon -Stability stable -Extension_version $semver

View File

@@ -3,6 +3,8 @@ get_phalcon_version() {
if [ "$extension" = "phalcon5" ]; then if [ "$extension" = "phalcon5" ]; then
if [ "${version:?}" = "7.4" ]; then if [ "${version:?}" = "7.4" ]; then
echo '5.4.0' echo '5.4.0'
elif [ "${version:?}" = "8.0" ]; then
echo '5.10.0'
else else
get_pecl_version phalcon stable 5 get_pecl_version phalcon stable 5
fi fi

View File

@@ -36,7 +36,10 @@ get_openssl_suffix() {
change_library_paths() { change_library_paths() {
if [ "$os" = "Darwin" ]; then if [ "$os" = "Darwin" ]; then
otool -L "${ext_dir:?}"/relay.so | grep -q 'ssl.1' && openssl_version='1.1' || openssl_version='3' otool -L "${ext_dir:?}"/relay.so | grep -q 'ssl.1' && openssl_version='1.1' || openssl_version='3'
[ -e "${brew_prefix:?}"/opt/openssl@"$openssl_version" ] || brew install openssl@"$openssl_version" [ -e "${brew_prefix:?}"/opt/openssl@"$openssl_version" ] || {
safe_brew install --skip-link openssl@"$openssl_version" &&
brew link --overwrite --force openssl@"$openssl_version"
}
dylibs="$(otool -L "${ext_dir:?}"/relay.so | grep -Eo '.*\.dylib' | cut -f1 -d ' ')" dylibs="$(otool -L "${ext_dir:?}"/relay.so | grep -Eo '.*\.dylib' | cut -f1 -d ' ')"
install_name_tool -change "$(echo "${dylibs}" | grep -E "libzstd.*dylib" | xargs)" "$brew_prefix"/opt/zstd/lib/libzstd.dylib "$ext_dir"/relay.so install_name_tool -change "$(echo "${dylibs}" | grep -E "libzstd.*dylib" | xargs)" "$brew_prefix"/opt/zstd/lib/libzstd.dylib "$ext_dir"/relay.so
install_name_tool -change "$(echo "${dylibs}" | grep -E "liblz4.*dylib" | xargs)" "$brew_prefix"/opt/lz4/lib/liblz4.dylib "$ext_dir"/relay.so install_name_tool -change "$(echo "${dylibs}" | grep -E "liblz4.*dylib" | xargs)" "$brew_prefix"/opt/lz4/lib/liblz4.dylib "$ext_dir"/relay.so
@@ -54,7 +57,7 @@ add_relay_dependencies() {
if [ "$os" = "Darwin" ]; then if [ "$os" = "Darwin" ]; then
. "${0%/*}"/tools/brew.sh . "${0%/*}"/tools/brew.sh
configure_brew configure_brew
brew install lz4 hiredis zstd concurrencykit safe_brew install lz4 hiredis zstd concurrencykit
fi fi
} }

View File

@@ -60,9 +60,11 @@ add_linux_libs() {
add_darwin_libs() { add_darwin_libs() {
local lib=$1 local lib=$1
if ! check_lib "$lib"; then if ! check_lib "$lib"; then
brew install "$lib" >/dev/null 2>&1 || true
if [[ "$lib" = *@* ]]; then if [[ "$lib" = *@* ]]; then
safe_brew install --skip-link "$lib" >/dev/null 2>&1 || true
brew link --overwrite --force "$lib" >/dev/null 2>&1 || true brew link --overwrite --force "$lib" >/dev/null 2>&1 || true
else
safe_brew install "$lib" >/dev/null 2>&1 || true
fi fi
fi fi
add_lib_log "$lib" add_lib_log "$lib"

View File

@@ -9,6 +9,8 @@ Function Get-SqlsrvReleaseVersion() {
return '5.10.1' return '5.10.1'
} elseif ($version -eq '8.0') { } elseif ($version -eq '8.0') {
return '5.11.1' return '5.11.1'
} elseif ($version -match '8.[1-2]') {
return '5.12.0'
} else { } else {
return 'latest' return 'latest'
} }

View File

@@ -6,6 +6,8 @@ get_sqlsrv_version() {
echo '5.10.1' echo '5.10.1'
elif [[ "${version:?}" =~ 8.0 ]]; then elif [[ "${version:?}" =~ 8.0 ]]; then
echo '5.11.1' echo '5.11.1'
elif [[ "${version:?}" =~ 8.[1-2] ]]; then
echo '5.12.0'
else else
# Return an empty string so that pecl will install the latest version. # Return an empty string so that pecl will install the latest version.
echo '' echo ''

View File

@@ -127,6 +127,51 @@ setup_cached_versions() {
run_script "php-ubuntu" "$version" "${debug:?}" "${ts:?}" run_script "php-ubuntu" "$version" "${debug:?}" "${ts:?}"
} }
# Function to get the link path for an alternative.
alternative_link() {
case "$1" in
php-cgi-bin) echo "/usr/lib/cgi-bin/php" ;;
php-fpm) echo "/usr/sbin/php-fpm" ;;
php-fpm.sock) echo "/run/php/php-fpm.sock" ;;
*) echo "/usr/bin/$1" ;;
esac
}
# Function to get the target path for an alternative.
alternative_target() {
case "$1" in
php-cgi-bin) echo "/usr/lib/cgi-bin/php$version" ;;
php-fpm) echo "/usr/sbin/php-fpm$version" ;;
php-fpm.sock) echo "/run/php/php$version-fpm.sock" ;;
*) echo "/usr/bin/$1$version" ;;
esac
}
# Function to register an alternative if the versioned binary exists but is missing.
register_alternative() {
local tool=$1
local link target priority state_file
target="$(alternative_target "$tool")"
[ -e "$target" ] || return 0
state_file="/var/lib/dpkg/alternatives/$tool"
if sudo test -r "$state_file" && sudo grep -Fxq "$target" "$state_file"; then
return 0
fi
link="$(alternative_link "$tool")"
priority="${version//./}"
sudo update-alternatives --install "$link" "$tool" "$target" "$priority" >/dev/null 2>&1
}
# Function to register and switch an alternative.
set_alternative() {
local tool=$1
local target
target="$(alternative_target "$tool")"
[ -e "$target" ] || return 0
register_alternative "$tool" || return 1
sudo update-alternatives --set "$tool" "$target" >/dev/null 2>&1
}
# Function to add PECL. # Function to add PECL.
add_pecl() { add_pecl() {
add_devtools phpize >/dev/null 2>&1 add_devtools phpize >/dev/null 2>&1
@@ -143,16 +188,11 @@ switch_version() {
tools=("$@") tools=("$@")
to_wait=() to_wait=()
if ! (( ${#tools[@]} )); then if ! (( ${#tools[@]} )); then
tools+=(pear pecl php phar phar.phar php-cgi php-config phpize phpdbg) tools+=(php-cgi-bin php-fpm php-fpm.sock pear pecl php phar phar.phar php-cgi php-config phpize phpdbg)
[ -e /usr/lib/cgi-bin/php"$version" ] && sudo update-alternatives --set php-cgi-bin /usr/lib/cgi-bin/php"$version" & to_wait+=($!)
[ -e /usr/sbin/php-fpm"$version" ] && sudo update-alternatives --set php-fpm /usr/sbin/php-fpm"$version" & to_wait+=($!)
[ -e /run/php/php"$version"-fpm.sock ] && sudo update-alternatives --set php-fpm.sock /run/php/php"$version"-fpm.sock & to_wait+=($!)
fi fi
for tool in "${tools[@]}"; do for tool in "${tools[@]}"; do
if [ -e "/usr/bin/$tool$version" ]; then set_alternative "$tool" &
sudo update-alternatives --set "$tool" /usr/bin/"$tool$version" & to_wait+=($!)
to_wait+=($!)
fi
done done
wait "${to_wait[@]}" wait "${to_wait[@]}"
} }
@@ -187,7 +227,7 @@ update_php() {
# Function to install PHP. # Function to install PHP.
add_php() { add_php() {
if [ "${runner:?}" = "self-hosted" ] || [ "${use_package_cache:-true}" = "false" ]; then if [ "${runner:?}" = "self-hosted" ] || [ "${use_package_cache:-true}" = "false" ]; then
if [[ "$version" =~ ${nightly_versions:?} || "$ts" = "zts" ]]; then if [[ "$version" =~ ${php_builder_versions:?} || "$ts" = "zts" ]]; then
setup_php_builder setup_php_builder
else else
add_packaged_php add_packaged_php

View File

@@ -3,6 +3,7 @@ $composer_home = "$env:APPDATA\Composer"
$composer_bin = "$composer_home\vendor\bin" $composer_bin = "$composer_home\vendor\bin"
$composer_json = "$composer_home\composer.json" $composer_json = "$composer_home\composer.json"
$composer_lock = "$composer_home\composer.lock" $composer_lock = "$composer_home\composer.lock"
$skip_composer_github_auth = $false
# Function to configure composer. # Function to configure composer.
Function Edit-ComposerConfig() { Function Edit-ComposerConfig() {
@@ -23,6 +24,7 @@ Function Edit-ComposerConfig() {
if (-not(Test-Path $composer_json)) { if (-not(Test-Path $composer_json)) {
Set-Content -Path $composer_json -Value "{}" Set-Content -Path $composer_json -Value "{}"
} }
Get-ToolVersion "composer" $null | Out-Null
Set-ComposerEnv Set-ComposerEnv
Add-Path $composer_bin Add-Path $composer_bin
Set-ComposerAuth Set-ComposerAuth
@@ -74,8 +76,18 @@ function Test-GitHubPublicAccess {
} }
} }
Function Write-ComposerGhAuthNoOpWarning() {
$message = (Get-Content (Join-Path $src 'configs\composer-gh-auth-warn') -Raw).Trim().Replace('%s', $composer_version)
if($env:fail_fast -eq 'true') {
Add-Log "$cross" "composer" $message
} else {
Write-Output "::warning::$message"
}
}
# Function to setup authentication in composer. # Function to setup authentication in composer.
Function Set-ComposerAuth() { Function Set-ComposerAuth() {
$token = if ($env:COMPOSER_TOKEN) { $env:COMPOSER_TOKEN } else { $env:GITHUB_TOKEN }
if(Test-Path env:COMPOSER_AUTH_JSON) { if(Test-Path env:COMPOSER_AUTH_JSON) {
if(Test-Json -JSON $env:COMPOSER_AUTH_JSON) { if(Test-Json -JSON $env:COMPOSER_AUTH_JSON) {
Set-Content -Path $composer_home\auth.json -Value $env:COMPOSER_AUTH_JSON Set-Content -Path $composer_home\auth.json -Value $env:COMPOSER_AUTH_JSON
@@ -83,13 +95,18 @@ Function Set-ComposerAuth() {
Add-Log "$cross" "composer" "Could not parse COMPOSER_AUTH_JSON as valid JSON" Add-Log "$cross" "composer" "Could not parse COMPOSER_AUTH_JSON as valid JSON"
} }
} }
if($skip_composer_github_auth) {
Write-ComposerGhAuthNoOpWarning
}
$composer_auth = @() $composer_auth = @()
if(Test-Path env:PACKAGIST_TOKEN) { if(Test-Path env:PACKAGIST_TOKEN) {
$composer_auth += '"http-basic": {"repo.packagist.com": { "username": "token", "password": "' + $env:PACKAGIST_TOKEN + '"}}' $composer_auth += '"http-basic": {"repo.packagist.com": { "username": "token", "password": "' + $env:PACKAGIST_TOKEN + '"}}'
} }
$write_token = $true $write_token = $true
$token = if ($env:COMPOSER_TOKEN) { $env:COMPOSER_TOKEN } else { $env:GITHUB_TOKEN }
if ($token) { if ($token) {
if ($skip_composer_github_auth) {
$write_token = $false
}
if ($env:GITHUB_SERVER_URL -ne "https://github.com" -and -not(Test-GitHubPublicAccess $token)) { if ($env:GITHUB_SERVER_URL -ne "https://github.com" -and -not(Test-GitHubPublicAccess $token)) {
$write_token = $false $write_token = $false
} }
@@ -115,6 +132,18 @@ Function Set-ComposerEnv() {
} }
} }
# Function to identify latest-like URLs that should bypass the persistent cache.
Function Test-MutableToolUrl() {
Param(
[Parameter(Position = 0, Mandatory = $true)]
[string]
$Url
)
$mutableUrlRegex = '(^|[/?#._=-])(latest|stable|preview|snapshot|nightly|master)([/?#._=-]|$)|/releases/latest/download/'
$versionLikeRegex = '(^|[^0-9])[0-9]+\.[0-9]+([.-][0-9A-Za-z]+)*'
return ($Url -match $mutableUrlRegex) -or (($Url -match '\.phar([?#].*)?$') -and -not ($Url -match $versionLikeRegex))
}
# Function to extract tool version. # Function to extract tool version.
Function Get-ToolVersion() { Function Get-ToolVersion() {
Param ( Param (
@@ -158,12 +187,7 @@ Function Add-ToolsHelper() {
} elseif($tool -eq "cs2pr") { } elseif($tool -eq "cs2pr") {
(Get-Content $bin_dir/cs2pr).replace('exit(9)', 'exit(0)') | Set-Content $bin_dir/cs2pr (Get-Content $bin_dir/cs2pr).replace('exit(9)', 'exit(0)') | Set-Content $bin_dir/cs2pr
} elseif($tool -eq "deployer") { } elseif($tool -eq "deployer") {
if(Test-Path $composer_bin\deployer.phar.bat) { Copy-Item $bin_dir\deployer.bat -Destination $bin_dir\dep.bat
Copy-Item $composer_bin\deployer.phar.bat -Destination $composer_bin\dep.bat
}
if(Test-Path $composer_bin\dep.bat) {
Copy-Item $composer_bin\dep.bat -Destination $composer_bin\deployer.bat
}
} elseif($tool -eq "phan") { } elseif($tool -eq "phan") {
$extensions += @('fileinfo', 'ast') $extensions += @('fileinfo', 'ast')
} elseif($tool -eq "phinx") { } elseif($tool -eq "phinx") {
@@ -203,32 +227,58 @@ Function Add-Tool() {
[ValidateNotNull()] [ValidateNotNull()]
$tool, $tool,
[Parameter(Position = 2, Mandatory = $false)] [Parameter(Position = 2, Mandatory = $false)]
$ver_param $ver_param,
[Parameter(Position = 3, Mandatory = $false)]
$skip_composer_github_auth
) )
if (Test-Path $bin_dir\$tool) { if($tool -eq "composer") {
Copy-Item $bin_dir\$tool -Destination $bin_dir\$tool.old -Force $script:skip_composer_github_auth = $skip_composer_github_auth -eq 'true'
} }
$urls = $urls -split ','
$tool_path = "$bin_dir\$tool" $tool_path = "$bin_dir\$tool"
foreach ($url in $urls){ $is_exe = ((($urls[0] | Split-Path -Extension).ToLowerInvariant()) -eq '.exe')
if (($url | Split-Path -Extension) -eq ".exe") { if ($is_exe) { $tool_path = "$tool_path.exe" }
$tool_path = "$tool_path.exe" $tool_ext = if ($is_exe) { '.exe' } else { '' }
} $url_stream = [System.IO.MemoryStream]::New([System.Text.Encoding]::UTF8.GetBytes($urls[0]))
try { $cache_key = (Get-FileHash -InputStream $url_stream -Algorithm SHA256).Hash.Substring(0, 16)
$status_code = (Invoke-WebRequest -Passthru -Uri $url -OutFile $tool_path).StatusCode $cache_path = "$env:TEMP\$tool-$cache_key$tool_ext"
} catch { $use_cache = -not (Test-MutableToolUrl $urls[0])
if($url -match '.*github.com.*releases.*latest.*') { $status_code = 200
try { if ($use_cache -and (Test-Path $cache_path -PathType Leaf)) {
$url = $url.replace("releases/latest/download", "releases/download/" + ([regex]::match((Get-File -Url ($url.split('/release')[0] + "/releases")).Content, "([0-9]+\.[0-9]+\.[0-9]+)/" + ($url.Substring($url.LastIndexOf("/") + 1))).Groups[0].Value).split('/')[0]) Copy-Item $cache_path -Destination $tool_path -Force
$status_code = (Invoke-WebRequest -Passthru -Uri $url -OutFile $tool_path).StatusCode } else {
} catch { } $backup_path = "$tool_path.bak"
if (Test-Path $tool_path) { Copy-Item $tool_path -Destination $backup_path -Force }
foreach ($url in $urls){
try {
$status_code = (Invoke-WebRequest -Passthru -Uri $url -OutFile $tool_path).StatusCode
} catch {
if($url -match '.*github.com.*releases.*latest.*') {
try {
$url = $url.replace("releases/latest/download", "releases/download/" + ([regex]::match((Get-File -Url ($url.split('/release')[0] + "/releases")).Content, "([0-9]+\.[0-9]+\.[0-9]+)/" + ($url.Substring($url.LastIndexOf("/") + 1))).Groups[0].Value).split('/')[0])
$status_code = (Invoke-WebRequest -Passthru -Uri $url -OutFile $tool_path).StatusCode
} catch {
$status_code = 0
}
} else {
$status_code = 0
}
}
if($status_code -eq 200 -and (Test-Path $tool_path)) {
if ($use_cache) {
Copy-Item $tool_path -Destination $cache_path -Force
}
break
} }
} }
if($status_code -eq 200 -and (Test-Path $tool_path)) { if ($status_code -ne 200 -and (Test-Path $backup_path)) {
break Copy-Item $backup_path -Destination $tool_path -Force
} }
Remove-Item $backup_path -Force -ErrorAction SilentlyContinue
} }
if (((Get-ChildItem -Path $bin_dir/* | Where-Object Name -Match "^$tool(.exe|.phar)*$").Count -gt 0)) { $escaped_tool = [regex]::Escape($tool)
if (((Get-ChildItem -Path $bin_dir/* | Where-Object Name -Match "^$escaped_tool(\.exe|\.phar)?$").Count -gt 0)) {
$bat_content = @() $bat_content = @()
$bat_content += "@ECHO off" $bat_content += "@ECHO off"
$bat_content += "setlocal DISABLEDELAYEDEXPANSION" $bat_content += "setlocal DISABLEDELAYEDEXPANSION"
@@ -242,8 +292,6 @@ Function Add-Tool() {
} else { } else {
if($tool -eq "composer") { if($tool -eq "composer") {
$env:fail_fast = 'true' $env:fail_fast = 'true'
} elseif (Test-Path $bin_dir\$tool.old) {
Copy-Item $bin_dir\$tool.old -Destination $bin_dir\$tool -Force
} }
Add-Log $cross $tool "Could not add $tool" Add-Log $cross $tool "Could not add $tool"
} }

View File

@@ -3,6 +3,7 @@ export composer_home="$HOME/.composer"
export composer_bin="$composer_home/vendor/bin" export composer_bin="$composer_home/vendor/bin"
export composer_json="$composer_home/composer.json" export composer_json="$composer_home/composer.json"
export composer_lock="$composer_home/composer.lock" export composer_lock="$composer_home/composer.lock"
skip_composer_github_auth=false
# Function to extract tool version. # Function to extract tool version.
get_tool_version() { get_tool_version() {
@@ -41,6 +42,7 @@ configure_composer() {
echo '{}' | tee "$composer_json" >/dev/null echo '{}' | tee "$composer_json" >/dev/null
chmod 644 "$composer_json" chmod 644 "$composer_json"
fi fi
get_tool_version composer >/dev/null
set_composer_env set_composer_env
add_path "$composer_bin" add_path "$composer_bin"
set_composer_auth set_composer_auth
@@ -70,22 +72,39 @@ can_access_public_github() {
curl --fail -s -H "Authorization: token $1" 'https://api.github.com/' >/dev/null 2>&1 curl --fail -s -H "Authorization: token $1" 'https://api.github.com/' >/dev/null 2>&1
} }
composer_gh_auth_no_op() {
local message
message="$(<"${src:?}"/configs/composer-gh-auth-warn)"
message="${message//%s/$composer_version}"
if [ "${fail_fast:-false}" = "true" ]; then
add_log "${cross:?}" "composer" "$message"
else
echo "::warning::$message"
fi
}
# Function to setup authentication in composer. # Function to setup authentication in composer.
set_composer_auth() { set_composer_auth() {
if [ -n "$COMPOSER_AUTH_JSON" ]; then token="${COMPOSER_TOKEN:-$GITHUB_TOKEN}"
if php -r "json_decode('$COMPOSER_AUTH_JSON'); if(json_last_error() !== JSON_ERROR_NONE) { throw new Exception('invalid json'); }"; then if [ -n "${COMPOSER_AUTH_JSON:-}" ]; then
echo "$COMPOSER_AUTH_JSON" | tee "$composer_home/auth.json" >/dev/null if printf '%s' "$COMPOSER_AUTH_JSON" | jq -e . >/dev/null; then
printf '%s' "$COMPOSER_AUTH_JSON" | tee "$composer_home/auth.json" >/dev/null
else else
add_log "${cross:?}" "composer" "Could not parse COMPOSER_AUTH_JSON as valid JSON" add_log "${cross:?}" "composer" "Could not parse COMPOSER_AUTH_JSON as valid JSON"
fi fi
fi fi
if [ "$skip_composer_github_auth" = "true" ]; then
composer_gh_auth_no_op
fi
composer_auth=() composer_auth=()
if [ -n "$PACKAGIST_TOKEN" ]; then if [ -n "$PACKAGIST_TOKEN" ]; then
composer_auth+=( '"http-basic": {"repo.packagist.com": { "username": "token", "password": "'"$PACKAGIST_TOKEN"'"}}' ) composer_auth+=( '"http-basic": {"repo.packagist.com": { "username": "token", "password": "'"$PACKAGIST_TOKEN"'"}}' )
fi fi
token="${COMPOSER_TOKEN:-$GITHUB_TOKEN}"
if [ -n "$token" ]; then if [ -n "$token" ]; then
write_token=true write_token=true
if [ "$skip_composer_github_auth" = "true" ]; then
write_token=false
fi
if [ "$GITHUB_SERVER_URL" != "https://github.com" ]; then if [ "$GITHUB_SERVER_URL" != "https://github.com" ]; then
can_access_public_github "$token" || write_token=false can_access_public_github "$token" || write_token=false
fi fi
@@ -113,6 +132,16 @@ set_composer_env() {
fi fi
} }
# Function to identify latest-like URLs that should bypass the persistent cache.
is_mutable_tool_url() {
local tool_url=$1
local mutable_url_regex='(^|[/?#._=-])(latest|stable|preview|snapshot|nightly|master)([/?#._=-]|$)|/releases/latest/download/'
local version_like_regex='(^|[^0-9])[0-9]+\.[0-9]+([.-][0-9A-Za-z]+)*'
[[ "$tool_url" =~ $mutable_url_regex ]] && return 0
[[ "$tool_url" =~ \.phar([?#].*)?$ && ! "$tool_url" =~ $version_like_regex ]] && return 0
return 1
}
# Helper function to configure tools. # Helper function to configure tools.
add_tools_helper() { add_tools_helper() {
tool=$1 tool=$1
@@ -123,19 +152,15 @@ add_tools_helper() {
extensions+=(iconv mbstring phar sodium) extensions+=(iconv mbstring phar sodium)
elif [ "$tool" = "codeception" ]; then elif [ "$tool" = "codeception" ]; then
extensions+=(json mbstring) extensions+=(json mbstring)
sudo ln -s "$scoped_dir"/vendor/bin/codecept "$scoped_dir"/vendor/bin/codeception sudo ln -s "$scoped_dir"/vendor/bin/codecept "$scoped_dir"/vendor/bin/codeception 2>/dev/null || true
elif [ "$tool" = "composer" ]; then elif [ "$tool" = "composer" ]; then
configure_composer "$tool_path" configure_composer "$tool_path"
elif [ "$tool" = "cs2pr" ]; then elif [ "$tool" = "cs2pr" ]; then
sudo sed -i 's/\r$//; s/exit(9)/exit(0)/' "$tool_path" 2>/dev/null || sudo sed -i 's/\r$//; s/exit(9)/exit(0)/' "$tool_path" 2>/dev/null ||
sudo sed -i '' 's/\r$//; s/exit(9)/exit(0)/' "$tool_path" sudo sed -i '' 's/\r$//; s/exit(9)/exit(0)/' "$tool_path"
elif [ "$tool" = "deployer" ]; then elif [ "$tool" = "deployer" ]; then
if [ -e "$composer_bin"/deployer.phar ]; then sudo ln -s "$tool_path" "$tool_path_dir"/deployer 2>/dev/null || true
sudo ln -s "$composer_bin"/deployer.phar "$composer_bin"/dep sudo ln -s "$tool_path" "$tool_path_dir"/dep 2>/dev/null || true
fi
if [ -e "$composer_bin"/dep ]; then
sudo ln -s "$composer_bin"/dep "$composer_bin"/deployer
fi
elif [ "$tool" = "phan" ]; then elif [ "$tool" = "phan" ]; then
extensions+=(fileinfo ast) extensions+=(fileinfo ast)
elif [ "$tool" = "phinx" ]; then elif [ "$tool" = "phinx" ]; then
@@ -151,7 +176,7 @@ add_tools_helper() {
elif [ "$tool" = "phpDocumentor" ]; then elif [ "$tool" = "phpDocumentor" ]; then
extensions+=(ctype hash json fileinfo iconv mbstring simplexml xml) extensions+=(ctype hash json fileinfo iconv mbstring simplexml xml)
sudo ln -s "$tool_path" "$tool_path_dir"/phpdocumentor 2>/dev/null || true sudo ln -s "$tool_path" "$tool_path_dir"/phpdocumentor 2>/dev/null || true
sudo ln -s "$tool_path" "$tool_path_dir"/phpdoc sudo ln -s "$tool_path" "$tool_path_dir"/phpdoc 2>/dev/null || true
elif [ "$tool" = "phpunit" ]; then elif [ "$tool" = "phpunit" ]; then
extensions+=(dom json libxml mbstring xml xmlwriter) extensions+=(dom json libxml mbstring xml xmlwriter)
elif [ "$tool" = "phpunit-bridge" ]; then elif [ "$tool" = "phpunit-bridge" ]; then
@@ -162,9 +187,9 @@ add_tools_helper() {
fi fi
elif [ "$tool" = "vapor-cli" ]; then elif [ "$tool" = "vapor-cli" ]; then
extensions+=(fileinfo json mbstring zip simplexml) extensions+=(fileinfo json mbstring zip simplexml)
sudo ln -s "$scoped_dir"/vendor/bin/vapor "$scoped_dir"/vendor/bin/vapor-cli sudo ln -s "$scoped_dir"/vendor/bin/vapor "$scoped_dir"/vendor/bin/vapor-cli 2>/dev/null || true
elif [ "$tool" = wp-cli ]; then elif [ "$tool" = wp-cli ]; then
sudo ln -s "$tool_path" "$tool_path_dir"/"${tool%-*}" sudo ln -s "$tool_path" "$tool_path_dir"/"${tool%-*}" 2>/dev/null || true
fi fi
for extension in "${extensions[@]}"; do for extension in "${extensions[@]}"; do
add_extension "$extension" extension >/dev/null 2>&1 add_extension "$extension" extension >/dev/null 2>&1
@@ -176,29 +201,48 @@ add_tool() {
url=$1 url=$1
tool=$2 tool=$2
ver_param=$3 ver_param=$3
if [ "$tool" = "composer" ]; then
skip_composer_github_auth="${4:-false}"
fi
tool_path="$tool_path_dir/$tool" tool_path="$tool_path_dir/$tool"
if ! [ -d "$tool_path_dir" ]; then if ! [ -d "$tool_path_dir" ]; then
sudo mkdir -p "$tool_path_dir" sudo mkdir -p "$tool_path_dir"
fi fi
add_path "$tool_path_dir" if ! [ -d "$tool_cache_path_dir" ]; then
if [ -e "$tool_path" ]; then sudo mkdir -p "$tool_cache_path_dir"
sudo cp -aL "$tool_path" /tmp/"$tool"
fi fi
add_path "$tool_path_dir" verify
add_path "$tool_cache_path_dir"
IFS="," read -r -a url <<<"$url" IFS="," read -r -a url <<<"$url"
status_code=$(get -v -e "$tool_path" "${url[@]}") cache_key=$(get_sha256 "${url[0]}" | head -c 16)
if [ "$status_code" != "200" ] && [[ "${url[0]}" =~ .*github.com.*releases.*latest.* ]]; then cache_path="$tool_cache_path_dir/${tool}-${cache_key}"
url[0]="${url[0]//releases\/latest\/download/releases/download/$(get -s -n "" "$(echo "${url[0]}" | cut -d '/' -f '1-5')/releases" | grep -Eo -m 1 "([0-9]+\.[0-9]+\.[0-9]+)/$(echo "${url[0]}" | sed -e "s/.*\///")" | cut -d '/' -f 1)}" use_cache=true
status_code=$(get -v -e "$tool_path" "${url[0]}") is_mutable_tool_url "${url[0]}" && use_cache=false
status_code="200"
if [ "$use_cache" = "true" ] && [ -f "$cache_path" ]; then
sudo cp -a "$cache_path" "$tool_path"
else
[ -f "$tool_path" ] && sudo cp -a "$tool_path" "$tool_path.bak"
status_code=$(get -v -e "$tool_path" "${url[@]}")
if [ "$status_code" != "200" ] && [[ "${url[0]}" =~ .*github.com.*releases.*latest.* ]]; then
url[0]="${url[0]//releases\/latest\/download/releases/download/$(get -s -n "" "$(echo "${url[0]}" | cut -d '/' -f '1-5')/releases" | grep -Eo -m 1 "([0-9]+\.[0-9]+\.[0-9]+)/$(echo "${url[0]}" | sed -e "s/.*\///")" | cut -d '/' -f 1)}"
status_code=$(get -v -e "$tool_path" "${url[0]}")
fi
if [ "$status_code" = "200" ]; then
[ "$use_cache" = "true" ] && sudo cp -a "$tool_path" "$cache_path"
elif [ -f "$tool_path.bak" ]; then
sudo mv "$tool_path.bak" "$tool_path"
fi
sudo rm -f "$tool_path.bak"
fi fi
if [ "$status_code" = "200" ]; then if [ "$status_code" = "200" ]; then
add_tools_helper "$tool" add_tools_helper "$tool"
tool_version=$(get_tool_version "$tool" "$ver_param") tool_version=$(get_tool_version "$tool" "$ver_param")
sudo ln -sfn "$tool_path" "$tool_cache_path_dir/$tool" 2>/dev/null || true
add_log "${tick:?}" "$tool" "Added $tool $tool_version" add_log "${tick:?}" "$tool" "Added $tool $tool_version"
else else
if [ "$tool" = "composer" ]; then if [ "$tool" = "composer" ]; then
export fail_fast=true export fail_fast=true
elif [ -e /tmp/"$tool" ]; then
sudo cp -a /tmp/"$tool" "$tool_path"
fi fi
if [ "$status_code" = "404" ]; then if [ "$status_code" = "404" ]; then
add_log "$cross" "$tool" "Failed to download $tool from ${url[*]}" add_log "$cross" "$tool" "Failed to download $tool from ${url[*]}"

View File

@@ -8,7 +8,7 @@ add_blackfire_linux() {
add_blackfire_darwin() { add_blackfire_darwin() {
sudo mkdir -p /usr/local/var/run sudo mkdir -p /usr/local/var/run
add_brew_tap blackfireio/homebrew-blackfire add_brew_tap blackfireio/homebrew-blackfire
brew install blackfire safe_brew install blackfire
} }
blackfire_config() { blackfire_config() {

View File

@@ -44,6 +44,135 @@ add_brew_bins_to_path() {
add_path "$brew_prefix"/sbin add_path "$brew_prefix"/sbin
} }
# Function to get file modification time.
get_file_mtime() {
local file=$1
if [ "$(uname -s)" = "Darwin" ]; then
stat -f "%m" "$file" 2>/dev/null || echo 0
else
stat -c "%Y" "$file" 2>/dev/null || echo 0
fi
}
# Function to terminate a process and its direct children.
terminate_process_tree() {
local pid=$1
local children child
children=$(pgrep -P "$pid" 2>/dev/null || true)
kill -TERM "$pid" >/dev/null 2>&1 || true
for child in $children; do
terminate_process_tree "$child"
done
sleep 2
kill -KILL "$pid" >/dev/null 2>&1 || true
for child in $children; do
terminate_process_tree "$child"
done
}
# Function to run a command with an inactivity watchdog.
run_with_inactivity_watchdog() {
local timeout_secs="${SETUP_PHP_BREW_INACTIVITY_TIMEOUT:-180}"
local poll_secs="${SETUP_PHP_BREW_WATCHDOG_POLL:-5}"
local tmp_dir stdout_fifo stderr_fifo stdout_log stderr_log timeout_file
local command_pid stdout_reader_pid stderr_reader_pid monitor_pid exit_code
tmp_dir="$(mktemp -d "${TMPDIR:-/tmp}/setup-php-brew.XXXXXX")" || return 1
stdout_fifo="$tmp_dir/stdout.fifo"
stderr_fifo="$tmp_dir/stderr.fifo"
stdout_log="$tmp_dir/stdout.log"
stderr_log="$tmp_dir/stderr.log"
timeout_file="$tmp_dir/timed_out"
mkfifo "$stdout_fifo" "$stderr_fifo" || {
rm -rf "$tmp_dir"
return 1
}
: >"$stdout_log"
: >"$stderr_log"
("$@" >"$stdout_fifo" 2>"$stderr_fifo") &
command_pid=$!
(
while IFS= read -r line || [ -n "$line" ]; do
printf '%s\n' "$line"
printf '%s\n' "$line" >>"$stdout_log"
done <"$stdout_fifo"
) &
stdout_reader_pid=$!
(
while IFS= read -r line || [ -n "$line" ]; do
printf '%s\n' "$line" >&2
printf '%s\n' "$line" >>"$stderr_log"
done <"$stderr_fifo"
) &
stderr_reader_pid=$!
(
local last_activity current_activity current_err_activity now
last_activity=$(get_file_mtime "$stdout_log")
current_err_activity=$(get_file_mtime "$stderr_log")
[ "$current_err_activity" -gt "$last_activity" ] && last_activity="$current_err_activity"
while kill -0 "$command_pid" >/dev/null 2>&1; do
sleep "$poll_secs"
current_activity=$(get_file_mtime "$stdout_log")
[ "$current_activity" -gt "$last_activity" ] && last_activity="$current_activity"
current_err_activity=$(get_file_mtime "$stderr_log")
[ "$current_err_activity" -gt "$last_activity" ] && last_activity="$current_err_activity"
now=$(date +%s)
if [ $((now - last_activity)) -ge "$timeout_secs" ]; then
printf "\nsetup-php: brew produced no output for %ss; terminating and retrying...\n" "$timeout_secs" >&2
: >"$timeout_file"
terminate_process_tree "$command_pid"
break
fi
done
) &
monitor_pid=$!
wait "$command_pid"
exit_code=$?
wait "$stdout_reader_pid" 2>/dev/null || true
wait "$stderr_reader_pid" 2>/dev/null || true
kill "$monitor_pid" >/dev/null 2>&1 || true
wait "$monitor_pid" 2>/dev/null || true
if [ -e "$timeout_file" ]; then
rm -rf "$tmp_dir"
return 124
fi
rm -rf "$tmp_dir"
return "$exit_code"
}
# Function to run brew with retries and an inactivity watchdog.
safe_brew() {
local max_attempts="${SETUP_PHP_BREW_RETRY_ATTEMPTS:-3}"
local attempt=1
local exit_code=0
if [ "${SETUP_PHP_BREW_WATCHDOG:-true}" = "false" ]; then
brew "$@"
return $?
fi
while [ "$attempt" -le "$max_attempts" ]; do
run_with_inactivity_watchdog brew "$@" && return 0
exit_code=$?
if [ "$attempt" -ge "$max_attempts" ]; then
return "$exit_code"
fi
printf "setup-php: retrying brew command (attempt %s/%s, exit %s)\n" "$((attempt + 1))" "$max_attempts" "$exit_code" >&2
sleep "$((attempt * 5))"
attempt=$((attempt + 1))
done
return "$exit_code"
}
# Function to add brew. # Function to add brew.
add_brew() { add_brew() {
brew_prefix="$(get_brew_prefix)" brew_prefix="$(get_brew_prefix)"
@@ -74,6 +203,7 @@ configure_brew() {
export HOMEBREW_NO_ENV_HINTS=1 export HOMEBREW_NO_ENV_HINTS=1
export HOMEBREW_NO_INSTALL_CLEANUP=1 export HOMEBREW_NO_INSTALL_CLEANUP=1
export HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK=1 export HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK=1
export HOMEBREW_DOWNLOAD_CONCURRENCY="${HOMEBREW_DOWNLOAD_CONCURRENCY:-6}"
export brew_opts export brew_opts
export brew_path export brew_path
export brew_path_dir export brew_path_dir

View File

@@ -4,7 +4,7 @@ add_bazel() {
add_list bazel/apt https://storage.googleapis.com/bazel-apt https://bazel.build/bazel-release.pub.gpg stable jdk1.8 add_list bazel/apt https://storage.googleapis.com/bazel-apt https://bazel.build/bazel-release.pub.gpg stable jdk1.8
install_packages bazel install_packages bazel
else else
brew install bazel safe_brew install bazel
fi fi
fi fi
} }
@@ -25,7 +25,7 @@ add_grpc_php_plugin_brew() {
. "${0%/*}"/tools/brew.sh . "${0%/*}"/tools/brew.sh
configure_brew configure_brew
[ -e /usr/local/bin/protoc ] && sudo mv /usr/local/bin/protoc /tmp/protoc && sudo mv /usr/local/include/google /tmp [ -e /usr/local/bin/protoc ] && sudo mv /usr/local/bin/protoc /tmp/protoc && sudo mv /usr/local/include/google /tmp
brew install grpc safe_brew install grpc
brew link --force --overwrite grpc >/dev/null 2>&1 brew link --force --overwrite grpc >/dev/null 2>&1
[ -e /tmp/protoc ] && sudo mv /tmp/protoc /usr/local/bin/protoc && sudo mv /tmp/google /usr/local/include/ [ -e /tmp/protoc ] && sudo mv /tmp/protoc /usr/local/bin/protoc && sudo mv /tmp/google /usr/local/include/
grpc_tag="v$(brew info grpc | grep "grpc:" | grep -Eo "[0-9]+\.[0-9]+\.[0-9]+")" grpc_tag="v$(brew info grpc | grep "grpc:" | grep -Eo "[0-9]+\.[0-9]+\.[0-9]+")"

View File

@@ -4,7 +4,8 @@ export cross="✗"
export curl_opts=(-sL) export curl_opts=(-sL)
export old_versions="5.[3-5]" export old_versions="5.[3-5]"
export jit_versions="8.[0-9]" export jit_versions="8.[0-9]"
export nightly_versions="8.[3-9]" export php_builder_versions="8.[3-9]"
export nightly_versions="8.[6-9]"
export xdebug3_versions="7.[2-4]|8.[0-9]" export xdebug3_versions="7.[2-4]|8.[0-9]"
export latest="releases/latest/download" export latest="releases/latest/download"
export github="https://github.com/shivammathur" export github="https://github.com/shivammathur"
@@ -58,6 +59,7 @@ read_env() {
-n "$ACT" || -n "$CONTAINER" ]] && _runner=self-hosted || _runner=github -n "$ACT" || -n "$CONTAINER" ]] && _runner=self-hosted || _runner=github
runner="${runner:-${RUNNER:-$_runner}}" runner="${runner:-${RUNNER:-$_runner}}"
tool_path_dir="${setup_php_tools_dir:-${SETUP_PHP_TOOLS_DIR:-/usr/local/bin}}" tool_path_dir="${setup_php_tools_dir:-${SETUP_PHP_TOOLS_DIR:-/usr/local/bin}}"
tool_cache_path_dir="${setup_php_tool_cache_dir:-${SETUP_PHP_TOOL_CACHE_DIR:-${RUNNER_TOOL_CACHE:-/opt/hostedtoolcache}/setup-php/tools}}"
if [[ "$runner" = "github" && $_runner = "self-hosted" ]]; then if [[ "$runner" = "github" && $_runner = "self-hosted" ]]; then
fail_fast=true fail_fast=true
@@ -79,6 +81,7 @@ read_env() {
export update export update
export ts export ts
export tool_path_dir export tool_path_dir
export tool_cache_path_dir
} }
# Function to create a lock. # Function to create a lock.
@@ -169,14 +172,15 @@ get_shell_profile() {
# Function to add a path to the PATH variable. # Function to add a path to the PATH variable.
add_path() { add_path() {
path_to_add=$1 path_to_add=$1
[[ ":$PATH:" == *":$path_to_add:"* ]] && return action=$2
[[ "$action" == "verify" && ":$PATH:" == *":$path_to_add:"* ]] && return
if [[ -n "$GITHUB_PATH" ]]; then if [[ -n "$GITHUB_PATH" ]]; then
echo "$path_to_add" | tee -a "$GITHUB_PATH" >/dev/null 2>&1 printf '%s\n%s' "$path_to_add" "$(grep -v "^${path_to_add}$" "$GITHUB_PATH" 2>/dev/null)" > "$GITHUB_PATH"
else else
profile=$(get_shell_profile) profile=$(get_shell_profile)
([ -e "$profile" ] && grep -q ":$path_to_add\"" "$profile" 2>/dev/null) || echo "export PATH=\"\${PATH:+\${PATH}:}\"$path_to_add" | sudo tee -a "$profile" >/dev/null 2>&1 ([ -e "$profile" ] && grep -q ":$path_to_add\"" "$profile" 2>/dev/null) || echo "export PATH=\"\${PATH:+\${PATH}:}\"$path_to_add" | sudo tee -a "$profile" >/dev/null 2>&1
fi fi
export PATH="${PATH:+${PATH}:}$path_to_add" [[ ":$PATH:" == *":$path_to_add:"* ]] || export PATH="${PATH:+${PATH}:}$path_to_add"
} }
# Function to add environment variables using a PATH. # Function to add environment variables using a PATH.

View File

@@ -81,9 +81,10 @@ Function Get-PathFromRegistry {
# Function to add a location to PATH. # Function to add a location to PATH.
Function Add-Path { Function Add-Path {
param( param(
[string]$PathItem [string]$PathItem,
[switch]$Force
) )
if("$env:PATH;".contains("$PathItem;")) { if(-not($Force) -and "$env:PATH;".contains("$PathItem;")) {
return return
} }
if ($env:GITHUB_PATH) { if ($env:GITHUB_PATH) {
@@ -202,16 +203,20 @@ Function Install-PSPackage() {
$cmdlet $cmdlet
) )
$module_path = "$bin_dir\$psm1_path.psm1" $module_path = "$bin_dir\$psm1_path.psm1"
if(-not (Test-Path $module_path -PathType Leaf)) { $imported = $false
$zip_file = "$bin_dir\$package.zip" try {
Get-File -Url $url -OutFile $zip_file if(-not (Test-Path $module_path -PathType Leaf)) {
Expand-Archive -Path $zip_file -DestinationPath $bin_dir -Force $zip_file = "$bin_dir\$package.zip"
} Get-File -Url $url -OutFile $zip_file
Import-Module $module_path Expand-Archive -Path $zip_file -DestinationPath $bin_dir -Force -ErrorAction Stop
if($null -eq (Get-Command $cmdlet -ErrorAction SilentlyContinue)) { }
Install-Module -Name $package -Force Import-Module $module_path -ErrorAction Stop
} else { $imported = $null -ne (Get-Command $cmdlet -ErrorAction SilentlyContinue)
} catch { }
if($imported) {
Add-ToProfile $current_profile "$package-search" "Import-Module $module_path" Add-ToProfile $current_profile "$package-search" "Import-Module $module_path"
} else {
Install-Module -Name $package -Force
} }
} }
@@ -375,6 +380,7 @@ if(-not($env:ImageOS) -and -not($env:ImageVersion)) {
if(-not(Test-Path -LiteralPath $current_profile)) { if(-not(Test-Path -LiteralPath $current_profile)) {
New-Item -Path $current_profile -ItemType "file" -Force >$null 2>&1 New-Item -Path $current_profile -ItemType "file" -Force >$null 2>&1
} }
Add-Path -PathItem $bin_dir -Force
} }
$src = Join-Path -Path $PSScriptRoot -ChildPath \.. $src = Join-Path -Path $PSScriptRoot -ChildPath \..

View File

@@ -5,14 +5,130 @@ import * as fetch from './fetch';
import * as packagist from './packagist'; import * as packagist from './packagist';
import * as utils from './utils'; import * as utils from './utils';
type RS = Record<string, string>; /**
type RSRS = Record<string, RS>; * Valid function names for custom tool handlers
*/
type ToolFunction =
| 'castor'
| 'composer'
| 'deployer'
| 'dev_tools'
| 'phive'
| 'blackfire_player'
| 'pecl'
| 'phing'
| 'phpunit'
| 'phpcpd'
| 'wp_cli';
interface IRef { /**
* Tool data interface containing all properties for tool installation
*/
export interface ToolData {
tool: string;
version: string;
os: string;
php_version: string;
github: string;
domain: string;
extension: string;
repository: string;
prefix: string;
verb: string;
fetch_latest: 'true' | 'false';
scope: string;
version_parameter: string;
version_prefix: string;
release: string;
packagist: string;
type?: string;
function?: ToolFunction;
alias?: string;
url: string;
uri?: string;
error?: string;
}
/**
* Input type for functions that may receive partial/unresolved tool data
* Used by getUrl, getLatestVersion etc. before version is fully resolved
*/
export type ToolInput = Omit<ToolData, 'version' | 'url'> & {version?: string};
/**
* Partial tool data from tools.json configuration
*/
interface ToolConfig {
tool?: string;
repository?: string;
type?: string;
function?: ToolFunction;
alias?: string;
domain?: string;
extension?: string;
fetch_latest?: 'true' | 'false';
scope?: string;
version_parameter?: string;
version_prefix?: string;
packagist?: string;
}
/**
* GitHub reference object from API response
*/
interface GitHubRef {
ref: string; ref: string;
node_id: string; node_id: string;
url: string; url: string;
object: RS; object: {
sha: string;
type: string;
url: string;
};
}
/**
* Deployer manifest entry
*/
interface DeployerManifestEntry {
version: string;
url: string;
}
export function skipGitHubAuthForComposerVersion(version: string): boolean {
if (!/^\d+\.\d+\.\d+(?:-[\w-]+)?$/.test(version)) {
return false;
}
return fs
.readFileSync(
path.join(__dirname, '../src/configs/composer-gh-auth-no-op'),
'utf8'
)
.trim()
.split(/\r?\n/)
.some(range => {
const [min, max] = range.trim().split(/\s+/);
return (
cv.compareVersions(version, min) >= 0 &&
cv.compareVersions(version, max) < 0
);
});
}
export function cleanComposerAuthJson(): void {
try {
const auth_json = process.env['COMPOSER_AUTH_JSON'] || '';
if (!auth_json.includes('github-oauth')) return;
const auth = JSON.parse(auth_json);
delete auth['github-oauth'];
if (!Object.keys(auth).length) {
delete process.env['COMPOSER_AUTH_JSON'];
} else {
process.env['COMPOSER_AUTH_JSON'] = JSON.stringify(auth);
}
} catch {
return;
}
} }
/** /**
@@ -20,7 +136,7 @@ interface IRef {
* *
* @param data * @param data
*/ */
export async function getSemverVersion(data: RS): Promise<string> { export async function getSemverVersion(data: ToolData): Promise<string> {
const fixSemver = (t: string): string => { const fixSemver = (t: string): string => {
if (/^\d+\.\d+\.\d+(-|$)/.test(t)) return t; if (/^\d+\.\d+\.\d+(-|$)/.test(t)) return t;
const m = t.match(/^(\d+\.\d+\.\d+)([A-Za-z]+[0-9A-Za-z.]+)$/); const m = t.match(/^(\d+\.\d+\.\d+)([A-Za-z]+[0-9A-Za-z.]+)$/);
@@ -31,14 +147,16 @@ export async function getSemverVersion(data: RS): Promise<string> {
const github_token: string = const github_token: string =
(await utils.readEnv('GITHUB_TOKEN')) || (await utils.readEnv('GITHUB_TOKEN')) ||
(await utils.readEnv('COMPOSER_TOKEN')); (await utils.readEnv('COMPOSER_TOKEN'));
const response: RS = await fetch.fetch(url, github_token); const response = await fetch.fetch(url, github_token);
if (response.error || response.data === '[]') { if (response.error || response.data === '[]') {
data['error'] = response.error ?? `No version found with prefix ${search}.`; data.error = response.error ?? `No version found with prefix ${search}.`;
return data['version']; return data.version;
} else { } else {
const refs: IRef[] = JSON.parse(response['data']); const refs: GitHubRef[] = JSON.parse(response.data);
const tags = refs const tags = refs
.map((i: IRef) => (i.ref?.split('/').pop() ?? '').replace(/^v(?=\d)/, '')) .map((i: GitHubRef) =>
(i.ref?.split('/').pop() ?? '').replace(/^v(?=\d)/, '')
)
.filter((t: string) => t.length > 0); .filter((t: string) => t.length > 0);
const fixedToOriginal = new Map<string, string>(); const fixedToOriginal = new Map<string, string>();
const fixed = tags.map(t => { const fixed = tags.map(t => {
@@ -46,14 +164,14 @@ export async function getSemverVersion(data: RS): Promise<string> {
fixedToOriginal.set(f, t); fixedToOriginal.set(f, t);
return f; return f;
}); });
fixed.sort((a, b) => { const sorted = fixed.toSorted((a, b) => {
try { try {
return cv.compareVersions(b, a); return cv.compareVersions(b, a);
} catch { } catch {
return b.localeCompare(a, 'en', {numeric: true, sensitivity: 'base'}); return b.localeCompare(a, 'en', {numeric: true, sensitivity: 'base'});
} }
}); });
return fixedToOriginal.get(fixed[0]) ?? fixed[0]; return fixedToOriginal.get(sorted[0]) ?? sorted[0];
} }
} }
@@ -62,25 +180,25 @@ export async function getSemverVersion(data: RS): Promise<string> {
* *
* @param data * @param data
*/ */
export async function getLatestVersion(data: RS): Promise<string> { export async function getLatestVersion(data: ToolInput): Promise<string> {
if (!data['version'] && data['fetch_latest'] === 'false') { if (!data.version && data.fetch_latest === 'false') {
return 'latest'; return 'latest';
} }
const resp: Record<string, string> = await fetch.fetch( if (data.fetch_latest === 'true' && !data.repository) {
`${data['github']}/${data['repository']}/releases.atom` return 'latest';
}
const resp = await fetch.fetch(
`${data.github}/${data.repository}/releases.atom`
); );
if (resp['data']) { if (resp.data) {
const releases: string[] = [ const releases: string[] = [
...resp['data'].matchAll(/releases\/tag\/([a-zA-Z]*)?(\d+.\d+.\d+)"/g) ...resp.data.matchAll(/releases\/tag\/([a-zA-Z]*)?(\d+\.\d+\.\d+)"/g)
].map(match => match[2]); ].map(match => match[2]);
return ( const sorted = releases.toSorted((a: string, b: string) =>
releases a.localeCompare(b, undefined, {numeric: true})
.sort((a: string, b: string) =>
a.localeCompare(b, undefined, {numeric: true})
)
.pop() || 'latest'
); );
return sorted.at(-1) || 'latest';
} }
return 'latest'; return 'latest';
} }
@@ -91,26 +209,29 @@ export async function getLatestVersion(data: RS): Promise<string> {
* @param version * @param version
* @param data * @param data
*/ */
export async function getVersion(version: string, data: RS): Promise<string> { export async function getVersion(
version: string,
data: ToolData
): Promise<string> {
// semver_regex - https://semver.org/ // semver_regex - https://semver.org/
const semver_regex = const semver_regex =
/^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/; /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/;
const composer_regex = /^composer:(stable|preview|snapshot|[1|2])$/; const composer_regex = /^composer:(stable|preview|snapshot|[12])$/;
const constraint_regex = /[><=^~]+.*/; const constraint_regex = /[><=^~]+.*/;
const major_minor_regex = /^\d+(\.\d+)?$/; const major_minor_regex = /^\d+(\.\d+)?$/;
data['version'] = version.replace(/v?(\d)/, '$1').replace(/\.x/, ''); data.version = version.replace(/v?(\d)/, '$1').replace(/\.x/, '');
switch (true) { switch (true) {
case composer_regex.test(data['release']): case composer_regex.test(data.release):
case semver_regex.test(data['version']): case semver_regex.test(data.version):
case constraint_regex.test(data['version']) && data['type'] === 'composer': case constraint_regex.test(data.version) && data.type === 'composer':
return data['version']; return data.version;
case major_minor_regex.test(data['version']) && data['type'] === 'composer': case major_minor_regex.test(data.version) && data.type === 'composer':
data['release'] = `${data['tool']}:${data['version']}.*`; data.release = `${data.tool}:${data.version}.*`;
return `${data['version']}.*`; return `${data.version}.*`;
case data['repository'] && major_minor_regex.test(data['version']): case !!data.repository && major_minor_regex.test(data.version):
return await getSemverVersion(data); return await getSemverVersion(data);
default: default:
return data['version'].replace(/[><=^~]*/, ''); return data.version.replace(/[^a-zA-Z0-9_.:@+,/-]/g, '');
} }
} }
@@ -120,11 +241,14 @@ export async function getVersion(version: string, data: RS): Promise<string> {
* @param release * @param release
* @param data * @param data
*/ */
export async function getRelease(release: string, data: RS): Promise<string> { export async function getRelease(
release: string,
data: ToolData
): Promise<string> {
release = release.includes('/') ? release.split('/')[1] : release; release = release.includes('/') ? release.split('/')[1] : release;
return release.includes(':') return release.includes(':')
? [data['tool'], release.split(':')[1]].join(':') ? [data.tool, release.split(':')[1]].join(':')
: data['tool']; : data.tool;
} }
/** /**
@@ -143,7 +267,7 @@ export async function filterList(tools_list: string[]): Promise<string[]> {
case matches[0] == undefined: case matches[0] == undefined:
break; break;
default: default:
composer = matches[matches.length - 1].replace(/v(\d\S*)/, '$1'); composer = matches.at(-1)!.replace(/v(\d\S*)/, '$1');
break; break;
} }
tools_list.unshift(composer); tools_list.unshift(composer);
@@ -155,26 +279,27 @@ export async function filterList(tools_list: string[]): Promise<string[]> {
* *
* @param data * @param data
*/ */
export async function getUrl(data: RS): Promise<string> { export async function getUrl(data: ToolInput): Promise<string> {
if ((data['version'] ?? 'latest') === 'latest') { const version = data.version ?? 'latest';
if (version === 'latest' || version === '') {
return [ return [
data['domain'], data.domain,
data['repository'], data.repository,
data['prefix'], data.prefix,
data['version'], 'latest',
data['verb'], data.verb,
data['tool'] + data['extension'] data.tool + data.extension
] ]
.filter(Boolean) .filter(Boolean)
.join('/'); .join('/');
} else { } else {
return [ return [
data['domain'], data.domain,
data['repository'], data.repository,
data['prefix'], data.prefix,
data['verb'], data.verb,
data['version_prefix'] + data['version'], data.version_prefix + data.version,
data['tool'] + data['extension'] data.tool + data.extension
] ]
.filter(Boolean) .filter(Boolean)
.join('/'); .join('/');
@@ -186,17 +311,17 @@ export async function getUrl(data: RS): Promise<string> {
* *
* @param data * @param data
*/ */
export async function getPharUrl(data: RS): Promise<string> { export async function getPharUrl(data: ToolData): Promise<string> {
if (data['version'] === 'latest') { if (data.version === 'latest') {
return data['domain'] + '/' + data['tool'] + '.phar'; return data.domain + '/' + data.tool + '.phar';
} else { } else {
return ( return (
data['domain'] + data.domain +
'/' + '/' +
data['tool'] + data.tool +
'-' + '-' +
data['version_prefix'] + data.version_prefix +
data['version'] + data.version +
'.phar' '.phar'
); );
} }
@@ -207,10 +332,10 @@ export async function getPharUrl(data: RS): Promise<string> {
* *
* @param data * @param data
*/ */
export async function addArchive(data: RS): Promise<string> { export async function addArchive(data: ToolData): Promise<string> {
return ( return (
(await utils.getCommand(data['os'], 'tool')) + (await utils.getCommand(data.os, 'tool')) +
(await utils.joins(data['url'], data['tool'], data['version_parameter'])) (await utils.joins(data.url, data.tool, data.version_parameter))
); );
} }
@@ -219,15 +344,12 @@ export async function addArchive(data: RS): Promise<string> {
* *
* @param data * @param data
*/ */
export async function addPackage(data: RS): Promise<string> { export async function addPackage(data: ToolData): Promise<string> {
const command = await utils.getCommand(data['os'], 'composer_tool'); const command = await utils.getCommand(data.os, 'composer_tool');
const parts: string[] = data['repository'].split('/'); const parts: string[] = data.repository.split('/');
const args: string = await utils.joins( const args = [parts[1], data.release, parts[0] + '/', data.scope]
parts[1], .map(a => utils.safeArg(a, data.os))
data['release'], .join(' ');
parts[0] + '/',
data['scope']
);
return command + args; return command + args;
} }
@@ -236,24 +358,24 @@ export async function addPackage(data: RS): Promise<string> {
* *
* @param data * @param data
*/ */
export async function addBlackfirePlayer(data: RS): Promise<string> { export async function addBlackfirePlayer(data: ToolData): Promise<string> {
switch (data['os']) { switch (data.os) {
case 'win32': case 'win32':
return await utils.addLog( return await utils.addLog(
'$cross', '$cross',
data['tool'], data.tool,
data['tool'] + ' is not a windows tool', data.tool + ' is not a windows tool',
'win32' 'win32'
); );
default: default:
if (data['version'] == 'latest') { if (data.version == 'latest') {
if (/5\.[5-6]|7\.0/.test(data['php_version'])) { if (/5\.[5-6]|7\.0/.test(data.php_version)) {
data['version'] = '1.9.3'; data.version = '1.9.3';
} else if (/7\.[1-4]|8\.0/.test(data['php_version'])) { } else if (/7\.[1-4]|8\.0/.test(data.php_version)) {
data['version'] = '1.22.0'; data.version = '1.22.0';
} }
} }
data['url'] = await getPharUrl(data); data.url = await getPharUrl(data);
return addArchive(data); return addArchive(data);
} }
} }
@@ -263,12 +385,12 @@ export async function addBlackfirePlayer(data: RS): Promise<string> {
* *
* @param data * @param data
*/ */
export async function addCastor(data: RS): Promise<string> { export async function addCastor(data: ToolData): Promise<string> {
data['tool'] = 'castor.' + data['os'].replace('win32', 'windows') + '-amd64'; data.tool = 'castor.' + data.os.replace('win32', 'windows') + '-amd64';
data['url'] = await getUrl(data); data.url = await getUrl(data);
data['tool'] = 'castor'; data.tool = 'castor';
data['version_parameter'] = fs.existsSync('castor.php') data.version_parameter = fs.existsSync('castor.php')
? data['version_parameter'] ? data.version_parameter
: ''; : '';
return await addArchive(data); return await addArchive(data);
} }
@@ -278,22 +400,23 @@ export async function addCastor(data: RS): Promise<string> {
* *
* @param data * @param data
*/ */
export async function addComposer(data: RS): Promise<string> { export async function addComposer(data: ToolData): Promise<string> {
const channel = data['version'].replace('latest', 'stable'); const channel = data.version.replace('latest', 'stable');
const github = data['github']; const github = data.github;
const getcomposer = data['domain']; const getcomposer = data.domain;
const cds = 'https://dl.cloudsmith.io'; const cds = 'https://dl.cloudsmith.io';
const spc = 'https://artifacts.setup-php.com'; const spc = 'https://artifacts.setup-php.com';
const filename = `composer-${data['php_version']}-${channel}.phar`; const filename = `composer-${data.php_version}-${channel}.phar`;
const releases_url = `${github}/shivammathur/composer-cache/releases/latest/download/${filename}`; const releases_url = `${github}/shivammathur/composer-cache/releases/latest/download/${filename}`;
const cds_url = `${cds}/public/shivammathur/composer-cache/raw/files/${filename}`; const cds_url = `${cds}/public/shivammathur/composer-cache/raw/files/${filename}`;
const spc_url = `${spc}/composer/${filename}`; const spc_url = `${spc}/composer/${filename}`;
const lts_url = `${getcomposer}/download/latest-2.2.x/composer.phar`; const lts_url = `${getcomposer}/download/latest-2.2.x/composer.phar`;
const is_lts = /^5\.[3-6]$|^7\.[0-1]$/.test(data['php_version']); const is_lts = /^5\.[3-6]$|^7\.[0-1]$/.test(data.php_version);
const channel_source_url = `${getcomposer}/composer-${channel}.phar`; const channel_source_url = `${getcomposer}/composer-${channel}.phar`;
const version_source_url = `${getcomposer}/download/${channel}/composer.phar`; const version_source_url = `${getcomposer}/download/${channel}/composer.phar`;
let cache_url = `${releases_url},${spc_url},${cds_url}`; let cache_url = `${releases_url},${spc_url},${cds_url}`;
let source_url = `${getcomposer}/composer.phar`; let source_url = `${getcomposer}/composer.phar`;
let skip_composer_github_auth = '';
switch (true) { switch (true) {
case /^snapshot$/.test(channel): case /^snapshot$/.test(channel):
source_url = is_lts ? lts_url : source_url; source_url = is_lts ? lts_url : source_url;
@@ -304,16 +427,20 @@ export async function addComposer(data: RS): Promise<string> {
case /^1$/.test(channel): case /^1$/.test(channel):
source_url = channel_source_url; source_url = channel_source_url;
break; break;
case /^\d+\.\d+\.\d+[\w-]*$/.test(data['version']): case /^\d+\.\d+\.\d+(?:-[\w-]+)?$/.test(data.version):
cache_url = `${github}/${data['repository']}/releases/download/${data['version']}/composer.phar`; if (skipGitHubAuthForComposerVersion(data.version)) {
cleanComposerAuthJson();
skip_composer_github_auth = ' true';
}
cache_url = `${github}/${data.repository}/releases/download/${data.version}/composer.phar`;
source_url = version_source_url; source_url = version_source_url;
break; break;
default: default:
source_url = is_lts ? lts_url : channel_source_url; source_url = is_lts ? lts_url : channel_source_url;
} }
const use_cache: boolean = (await utils.readEnv('NO_TOOLS_CACHE')) !== 'true'; const use_cache: boolean = (await utils.readEnv('NO_TOOLS_CACHE')) !== 'true';
data['url'] = use_cache ? `${cache_url},${source_url}` : source_url; data.url = use_cache ? `${cache_url},${source_url}` : source_url;
data['version_parameter'] = data['version']; data.version_parameter = data.version + skip_composer_github_auth;
return await addArchive(data); return await addArchive(data);
} }
@@ -322,27 +449,27 @@ export async function addComposer(data: RS): Promise<string> {
* *
* @param data * @param data
*/ */
export async function addDeployer(data: RS): Promise<string> { export async function addDeployer(data: ToolData): Promise<string> {
if (data['version'] === 'latest') { if (data.version === 'latest') {
data['url'] = data['domain'] + '/deployer.phar'; data.url = data.domain + '/deployer.phar';
} else { } else {
const manifest: RS = await fetch.fetch( const manifest = await fetch.fetch('https://deployer.org/manifest.json');
'https://deployer.org/manifest.json' const version_data: Record<string, DeployerManifestEntry> = JSON.parse(
manifest.data
); );
const version_data: RSRS = JSON.parse(manifest.data);
const version_key: string | undefined = Object.keys(version_data).find( const version_key: string | undefined = Object.keys(version_data).find(
(key: string) => { (key: string) => {
return version_data[key]['version'] === data['version']; return version_data[key].version === data.version;
} }
); );
if (version_key) { if (version_key) {
data['url'] = version_data[version_key]['url']; data.url = version_data[version_key].url;
} else { } else {
return await utils.addLog( return await utils.addLog(
'$cross', '$cross',
'deployer', 'deployer',
'Version missing in deployer manifest', 'Version missing in deployer manifest',
data['os'] data.os
); );
} }
} }
@@ -354,22 +481,22 @@ export async function addDeployer(data: RS): Promise<string> {
* *
* @param data * @param data
*/ */
export async function addDevTools(data: RS): Promise<string> { export async function addDevTools(data: ToolData): Promise<string> {
switch (data['os']) { switch (data.os) {
case 'linux': case 'linux':
case 'darwin': case 'darwin':
return 'add_devtools ' + data['tool']; return 'add_devtools ' + data.tool;
case 'win32': case 'win32':
return await utils.addLog( return await utils.addLog(
'$tick', '$tick',
data['tool'], data.tool,
data['tool'] + ' is not a windows tool', data.tool + ' is not a windows tool',
'win32' 'win32'
); );
default: default:
return await utils.log( return await utils.log(
'Platform ' + data['os'] + ' is not supported', 'Platform ' + data.os + ' is not supported',
data['os'], data.os,
'error' 'error'
); );
} }
@@ -380,8 +507,8 @@ export async function addDevTools(data: RS): Promise<string> {
* *
* @param data * @param data
*/ */
export async function addPECL(data: RS): Promise<string> { export async function addPECL(data: ToolData): Promise<string> {
return await utils.getCommand(data['os'], 'pecl'); return await utils.getCommand(data.os, 'pecl');
} }
/** /**
@@ -389,14 +516,13 @@ export async function addPECL(data: RS): Promise<string> {
* *
* @param data * @param data
*/ */
export async function addPhing(data: RS): Promise<string> { export async function addPhing(data: ToolData): Promise<string> {
data['url'] = data.url = data.domain + '/get/phing-' + data.version + data.extension;
data['domain'] + '/get/phing-' + data['version'] + data['extension']; if (data.version != 'latest') {
if (data['version'] != 'latest') { [data.prefix, data.verb] = ['releases', 'download'];
[data['prefix'], data['verb']] = ['releases', 'download']; data.domain = data.github;
data['domain'] = data['github']; data.extension = '-' + data.version + data.extension;
data['extension'] = '-' + data['version'] + data['extension']; data.url += ',' + (await getUrl(data));
data['url'] += ',' + (await getUrl(data));
} }
return await addArchive(data); return await addArchive(data);
} }
@@ -406,33 +532,33 @@ export async function addPhing(data: RS): Promise<string> {
* *
* @param data * @param data
*/ */
export async function addPhive(data: RS): Promise<string> { export async function addPhive(data: ToolData): Promise<string> {
switch (true) { switch (true) {
case /5\.[3-5]/.test(data['php_version']): case /5\.[3-5]/.test(data.php_version):
return await utils.addLog( return await utils.addLog(
'$cross', '$cross',
'phive', 'phive',
'Phive is not supported on PHP ' + data['php_version'], 'Phive is not supported on PHP ' + data.php_version,
data['os'] data.os
); );
case /5\.6|7\.0/.test(data['php_version']): case /5\.6|7\.0/.test(data.php_version):
data['version'] = '0.12.1'; data.version = '0.12.1';
break; break;
case /7\.1/.test(data['php_version']): case /7\.1/.test(data.php_version):
data['version'] = '0.13.5'; data.version = '0.13.5';
break; break;
case /7\.2/.test(data['php_version']): case /7\.2/.test(data.php_version):
data['version'] = '0.14.5'; data.version = '0.14.5';
break; break;
case /7\.3|7\.4/.test(data['php_version']): case /7\.3|7\.4/.test(data.php_version):
data['version'] = '0.15.3'; data.version = '0.15.3';
break; break;
case /^latest$/.test(data['version']): case /^latest$/.test(data.version):
data['version'] = await getLatestVersion(data); data.version = await getLatestVersion(data);
break; break;
} }
data['extension'] = '-' + data['version'] + data['extension']; data.extension = '-' + data.version + data.extension;
data['url'] = await getUrl(data); data.url = await getUrl(data);
return await addArchive(data); return await addArchive(data);
} }
@@ -441,16 +567,15 @@ export async function addPhive(data: RS): Promise<string> {
* *
* @param data * @param data
*/ */
export async function addPHPUnitTools(data: RS): Promise<string> { export async function addPHPUnitTools(data: ToolData): Promise<string> {
/* istanbul ignore next */ /* istanbul ignore next */
if (data['version'] === 'latest') { if (data.version === 'latest') {
data['version'] = data.version =
(await packagist.search(data['packagist'], data['php_version'])) ?? (await packagist.search(data.packagist, data.php_version)) ?? 'latest';
'latest';
} }
data['url'] = await getPharUrl(data); data.url = await getPharUrl(data);
if (data['url'].match(/-\d+/)) { if (data.url.match(/-\d+/)) {
data['url'] += ',' + data['url'].replace(/-(\d+)\.\d+\.\d+/, '-$1'); data.url += ',' + data.url.replace(/-(\d+)\.\d+\.\d+/, '-$1');
} }
return await addArchive(data); return await addArchive(data);
} }
@@ -460,13 +585,13 @@ export async function addPHPUnitTools(data: RS): Promise<string> {
* *
* @param data * @param data
*/ */
export async function addWPCLI(data: RS): Promise<string> { export async function addWPCLI(data: ToolData): Promise<string> {
if (data['version'] === 'latest') { if (data.version === 'latest') {
data['uri'] = 'wp-cli/builds/blob/gh-pages/phar/wp-cli.phar?raw=true'; data.uri = 'wp-cli/builds/blob/gh-pages/phar/wp-cli.phar?raw=true';
data['url'] = [data['domain'], data['uri']].join('/'); data.url = [data.domain, data.uri].join('/');
} else { } else {
data['extension'] = '-' + data['version'] + data['extension']; data.extension = '-' + data.version + data.extension;
data['url'] = await getUrl(data); data.url = await getUrl(data);
} }
return await addArchive(data); return await addArchive(data);
} }
@@ -482,56 +607,74 @@ export async function getData(
release: string, release: string,
php_version: string, php_version: string,
os: string os: string
): Promise<RS> { ): Promise<ToolData> {
const json_file_path = path.join(__dirname, '../src/configs/tools.json'); const json_file_path = path.join(__dirname, '../src/configs/tools.json');
const json_file: string = fs.readFileSync(json_file_path, 'utf8'); const json_file: string = fs.readFileSync(json_file_path, 'utf8');
const json_objects: RSRS = JSON.parse(json_file); const json_objects: Record<string, ToolConfig> = JSON.parse(json_file);
release = release.replace(/\s+/g, ''); release = release.replace(/\s+/g, '');
const parts: string[] = release.split(':'); const parts: string[] = release.split(':');
const tool = parts[0]; const tool = parts[0];
const version = parts[1]; const version = parts[1];
let data: RS; let config: ToolConfig & {tool: string};
if (Object.keys(json_objects).includes(tool)) { if (Object.hasOwn(json_objects, tool)) {
data = json_objects[tool]; config = {...json_objects[tool], tool};
data['tool'] = tool;
} else { } else {
const key: string | undefined = Object.keys(json_objects).find( const key: string | undefined = Object.keys(json_objects).find(
(key: string) => { (key: string) => {
return json_objects[key]['alias'] == tool; return json_objects[key].alias == tool;
} }
); );
if (key) { if (key) {
data = json_objects[key]; config = {...json_objects[key], tool: key};
data['tool'] = key; } else if (tool.includes('/')) {
} else { config = {
data = {
tool: tool.split('/')[1], tool: tool.split('/')[1],
repository: tool, repository: tool,
type: 'composer' type: 'composer'
}; };
data = !tool.includes('/') ? {tool: tool} : data; } else {
config = {tool};
} }
} }
data['github'] = 'https://github.com'; const github = 'https://github.com';
data['domain'] ??= data['github']; const domain = config.domain ?? github;
data['extension'] ??= '.phar'; const data: ToolData = {
data['os'] = os; tool: config.tool,
data['php_version'] = php_version; version: '',
data['packagist'] ??= data['repository']; url: '',
data['prefix'] = data['github'] === data['domain'] ? 'releases' : ''; os,
data['verb'] = data['github'] === data['domain'] ? 'download' : ''; php_version,
data['fetch_latest'] ??= 'false'; github,
data['scope'] ??= 'global'; domain,
data['version_parameter'] = JSON.stringify(data['version_parameter']) || ''; extension: config.extension ?? '.phar',
data['version_prefix'] ??= ''; repository: config.repository ?? '',
data['release'] = await getRelease(release, data); prefix: domain === github ? 'releases' : '',
data['version'] = version verb: domain === github ? 'download' : '',
fetch_latest: config.fetch_latest ?? 'false',
scope: config.scope ?? 'global',
version_parameter:
config.version_parameter != null
? JSON.stringify(config.version_parameter)
: '',
version_prefix: config.version_prefix ?? '',
release: '',
packagist: config.packagist ?? config.repository ?? '',
type: config.type,
function: config.function,
alias: config.alias
};
data.release = await getRelease(release, data);
data.version = version
? await getVersion(version, data) ? await getVersion(version, data)
: await getLatestVersion(data); : await getLatestVersion(data);
data.url = await getUrl(data);
return data; return data;
} }
export const functionRecord: Record<string, (data: RS) => Promise<string>> = { export const functionRecord: Record<
ToolFunction,
(data: ToolData) => Promise<string>
> = {
castor: addCastor, castor: addCastor,
composer: addComposer, composer: addComposer,
deployer: addDeployer, deployer: addDeployer,
@@ -565,43 +708,46 @@ export async function addTools(
} }
const tools_list = await filterList(await utils.CSVArray(tools_csv)); const tools_list = await filterList(await utils.CSVArray(tools_csv));
await utils.asyncForEach(tools_list, async function (release: string) { await utils.asyncForEach(tools_list, async function (release: string) {
const data: RS = await getData(release, php_version, os); const data: ToolData = await getData(release, php_version, os);
script += '\n'; script += '\n';
switch (true) { switch (true) {
case data['error'] !== undefined: case data.error !== undefined:
script += await utils.addLog( script += await utils.addLog('$cross', data.tool, data.error, data.os);
'$cross',
data['tool'],
data['error'],
data['os']
);
break; break;
case 'phar' === data['type']: case 'phar' === data.type:
data['url'] = await getUrl(data);
script += await addArchive(data); script += await addArchive(data);
break; break;
case 'composer' === data['type']: case 'composer' === data.type:
script += await addPackage(data); script += await addPackage(data);
break; break;
case 'custom-package' === data['type']: case 'custom-package' === data.type:
script += await utils.customPackage( script += await utils.customPackage(
data['tool'].split('-')[0], data.tool.split('-')[0],
'tools', 'tools',
data['version'], data.version,
data['os'] data.os
); );
break; break;
case 'custom-function' === data['type']: case 'custom-function' === data.type:
script += await functionRecord[data['function']](data); if (!data.function) {
script += await utils.addLog(
'$cross',
data.tool,
data.tool + ' has no function defined. Please report this issue.',
data.os
);
} else {
script += await functionRecord[data.function](data);
}
break; break;
case /^none$/.test(data['tool']): case /^none$/.test(data.tool):
break; break;
default: default:
script += await utils.addLog( script += await utils.addLog(
'$cross', '$cross',
data['tool'], data.tool,
'Tool ' + data['tool'] + ' is not supported', 'Tool ' + data.tool + ' is not supported',
data['os'] data.os
); );
break; break;
} }

View File

@@ -1,6 +1,6 @@
import fs from 'fs'; import fs from 'fs';
import * as path from 'path'; import * as path from 'path';
import * as core from '@actions/core'; import * as core from './core';
import * as fetch from './fetch'; import * as fetch from './fetch';
/** /**
@@ -62,15 +62,31 @@ export async function getManifestURLS(): Promise<string[]> {
*/ */
export async function parseVersion(version: string): Promise<string> { export async function parseVersion(version: string): Promise<string> {
switch (true) { switch (true) {
case /^(latest|lowest|highest|nightly|\d+\.x)$/.test(version): case /^pre(-installed)?$/.test(version):
return 'pre';
case /^(latest|lowest|highest|nightly|master|\d+\.x)$/.test(version):
for (const manifestURL of await getManifestURLS()) { for (const manifestURL of await getManifestURLS()) {
const fetchResult = await fetch.fetch(manifestURL); const fetchResult = await fetch.fetch(manifestURL);
if (fetchResult['data'] ?? false) { if (fetchResult['data'] ?? false) {
return JSON.parse(fetchResult['data'])[version]; const resolved: string | undefined = JSON.parse(fetchResult['data'])[
version
];
if (resolved === undefined) {
throw new Error(`Invalid PHP version: ${version.slice(0, 20)}`);
}
if (!/^\d+\.\d+$/.test(resolved)) {
throw new Error(
`Invalid PHP version in manifest: ${resolved.slice(0, 10)}`
);
}
return resolved;
} }
} }
throw new Error(`Could not fetch the PHP version manifest.`); throw new Error(`Could not fetch the PHP version manifest.`);
default: default:
if (!/^\d+(\.\d+){0,2}$/.test(version)) {
throw new Error(`Invalid PHP version: ${version.slice(0, 20)}`);
}
switch (true) { switch (true) {
case version.length > 1: case version.length > 1:
return version.slice(0, 3); return version.slice(0, 3);
@@ -86,20 +102,16 @@ export async function parseVersion(version: string): Promise<string> {
* @param ini_file * @param ini_file
*/ */
export async function parseIniFile(ini_file: string): Promise<string> { export async function parseIniFile(ini_file: string): Promise<string> {
switch (true) { if (/^(production|development|none)$/.test(ini_file)) {
case /^(production|development|none)$/.test(ini_file): return ini_file;
return ini_file;
case /php\.ini-(production|development)$/.test(ini_file):
return ini_file.split('-')[1];
default:
return 'production';
} }
const match = ini_file.match(/php\.ini-(production|development)$/);
return match ? match[1] : 'production';
} }
/** /**
* Async foreach loop * Async foreach loop using modern for...of pattern
* *
* @author https://github.com/Atinux
* @param array * @param array
* @param callback * @param callback
*/ */
@@ -111,8 +123,8 @@ export async function asyncForEach(
array: Array<string> array: Array<string>
) => Promise<void> ) => Promise<void>
): Promise<void> { ): Promise<void> {
for (let index = 0; index < array.length; index++) { for (const [index, element] of array.entries()) {
await callback(array[index], index, array); await callback(element, index, array);
} }
} }
@@ -173,10 +185,10 @@ export async function log(
export async function stepLog(message: string, os: string): Promise<string> { export async function stepLog(message: string, os: string): Promise<string> {
switch (os) { switch (os) {
case 'win32': case 'win32':
return 'Step-Log "' + message + '"'; return 'Step-Log "' + escapeForShell(message, os) + '"';
case 'linux': case 'linux':
case 'darwin': case 'darwin':
return 'step_log "' + message + '"'; return 'step_log "' + escapeForShell(message, os) + '"';
default: default:
return await log('Platform ' + os + ' is not supported', os, 'error'); return await log('Platform ' + os + ' is not supported', os, 'error');
} }
@@ -195,17 +207,40 @@ export async function addLog(
message: string, message: string,
os: string os: string
): Promise<string> { ): Promise<string> {
const sub = escapeForShell(subject, os);
const msg = escapeForShell(message, os);
switch (os) { switch (os) {
case 'win32': case 'win32':
return 'Add-Log "' + mark + '" "' + subject + '" "' + message + '"'; return `Add-Log "${mark}" "${sub}" "${msg}"`;
case 'linux': case 'linux':
case 'darwin': case 'darwin':
return 'add_log "' + mark + '" "' + subject + '" "' + message + '"'; return `add_log "${mark}" "${sub}" "${msg}"`;
default: default:
return await log('Platform ' + os + ' is not supported', os, 'error'); return await log('Platform ' + os + ' is not supported', os, 'error');
} }
} }
export function escapeForShell(value: string, os: string): string {
if (os === 'win32') {
return value.replace(/[`$"]/g, '`$&');
}
return value.replace(/[\\`$"]/g, '\\$&');
}
export function safeArg(value: string, os: string): string {
if (!/^[a-zA-Z0-9_./:@+,~^-]*$/.test(value)) {
return '"' + escapeForShell(value, os) + '"';
}
return value;
}
export function sanitizeShellInput(value: string, strict = false): string {
const pattern = strict
? /[$`"';|&(){}[\]\\<>*?\n\r\t]/g
: /[$`"';|&(){}[\]\\\n\r\t]/g;
return value.replace(pattern, '');
}
/** /**
* Function to break extension csv into an array * Function to break extension csv into an array
* *
@@ -225,11 +260,11 @@ export async function extensionArray(
.split(',') .split(',')
.map(function (extension: string) { .map(function (extension: string) {
extension = extension.trim().replace(/^\\\s*/, '');
if (/.+-.+\/.+@.+/.test(extension)) { if (/.+-.+\/.+@.+/.test(extension)) {
return extension; return extension;
} }
return extension return extension
.trim()
.toLowerCase() .toLowerCase()
.replace(/^(:)?(php[-_]|none|zend )|(-[^-]*)-/, '$1$3'); .replace(/^(:)?(php[-_]|none|zend )|(-[^-]*)-/, '$1$3');
}) })
@@ -432,22 +467,35 @@ export async function parseExtensionSource(
); );
} }
const VERSION_INPUT_REGEX =
/^(latest|lowest|highest|nightly|master|pre|pre-installed|\d+\.x|\d+(\.\d+){0,2})$/;
function validatePHPVersionInput(version: string, source: string): string {
if (!VERSION_INPUT_REGEX.test(version)) {
throw new Error(
`Invalid PHP version in ${source}: ${version.slice(0, 20)}`
);
}
return version;
}
/** /**
* Read php version from input or file * Read php version from input or file
*/ */
export async function readPHPVersion(): Promise<string> { export async function readPHPVersion(): Promise<string> {
const version = await getInput('php-version', false); const version = await getInput('php-version', false);
if (version) { if (version) {
return version; return validatePHPVersionInput(version, 'php-version input');
} }
const versionFile = const versionFile =
(await getInput('php-version-file', false)) || '.php-version'; (await getInput('php-version-file', false)) || '.php-version';
if (fs.existsSync(versionFile)) { if (fs.existsSync(versionFile)) {
const contents: string = fs.readFileSync(versionFile, 'utf8'); const contents: string = fs.readFileSync(versionFile, 'utf8');
const match: RegExpMatchArray | null = contents.match( const match = contents.match(/^(?:php\s)?(\d+\.\d+\.\d+)$/m);
/^(?:php\s)?(\d+\.\d+\.\d+)$/m return validatePHPVersionInput(
match ? match[1] : contents.trim(),
versionFile
); );
return match ? match[1] : contents.trim();
} else if (versionFile !== '.php-version') { } else if (versionFile !== '.php-version') {
throw new Error(`Could not find '${versionFile}' file.`); throw new Error(`Could not find '${versionFile}' file.`);
} }
@@ -457,11 +505,11 @@ export async function readPHPVersion(): Promise<string> {
if (fs.existsSync(composerLock)) { if (fs.existsSync(composerLock)) {
const lockFileContents = JSON.parse(fs.readFileSync(composerLock, 'utf8')); const lockFileContents = JSON.parse(fs.readFileSync(composerLock, 'utf8'));
/* istanbul ignore next */ /* istanbul ignore next */
if ( if (lockFileContents['platform-overrides']?.['php']) {
lockFileContents['platform-overrides'] && return validatePHPVersionInput(
lockFileContents['platform-overrides']['php'] lockFileContents['platform-overrides']['php'],
) { 'composer.lock platform-overrides.php'
return lockFileContents['platform-overrides']['php']; );
} }
} }
@@ -471,12 +519,11 @@ export async function readPHPVersion(): Promise<string> {
fs.readFileSync(composerJson, 'utf8') fs.readFileSync(composerJson, 'utf8')
); );
/* istanbul ignore next */ /* istanbul ignore next */
if ( if (composerFileContents['config']?.['platform']?.['php']) {
composerFileContents['config'] && return validatePHPVersionInput(
composerFileContents['config']['platform'] && composerFileContents['config']['platform']['php'],
composerFileContents['config']['platform']['php'] 'composer.json config.platform.php'
) { );
return composerFileContents['config']['platform']['php'];
} }
} }

View File

@@ -3,7 +3,7 @@
"declaration": true, "declaration": true,
"esModuleInterop": true, "esModuleInterop": true,
"lib": [ "lib": [
"ES2021" "ES2024"
], ],
"module": "commonjs", "module": "commonjs",
"moduleResolution": "node", "moduleResolution": "node",
@@ -13,7 +13,7 @@
"rootDir": "./src", "rootDir": "./src",
"sourceMap": true, "sourceMap": true,
"strict": true, "strict": true,
"target": "ES2021" "target": "ES2024"
}, },
"exclude": ["__tests__", "lib", "node_modules"] "exclude": ["__tests__", "lib", "node_modules"]
} }