10 Commits

Author SHA1 Message Date
Your Name
47dac3fa50 configuring infection 2024-10-19 18:49:51 +03:00
Your Name
06bdf2c9c2 create Action class 2024-10-19 18:40:14 +03:00
Your Name
b9541200da added PHP_VERSION for environment 2024-10-19 18:39:10 +03:00
Your Name
5eb9495529 update MapperTypeTest 2024-10-19 18:38:33 +03:00
Your Name
53455912a8 fixed error 2024-10-19 18:38:06 +03:00
Your Name
705a50d681 creating configuration and type casting 2024-10-18 22:27:29 +03:00
Your Name
7744d22039 added symfony/var-dumper 2024-10-18 19:43:15 +03:00
Your Name
1fbc211eed updating structure 2024-10-13 19:18:28 +03:00
A.Gudko
431cd5ca43 update php version 2024-08-29 22:16:57 +03:00
Your Name
af9260a35c updating structure 2024-08-25 18:34:01 +03:00
37 changed files with 8940 additions and 205 deletions

View File

@@ -0,0 +1,4 @@
.idea
.github
var
vendor

20
.editorconfig Normal file
View File

@@ -0,0 +1,20 @@
# editorconfig.org
root = true
[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
indent_style = space
indent_size = 4
trim_trailing_whitespace = true
[*.md]
trim_trailing_whitespace = false
[*.yml]
indent_size = 2
[Makefile]
indent_style = tab

7
.env.dist Normal file
View File

@@ -0,0 +1,7 @@
PHP_VERSION="8.3"
GITEA_INSTANCE_BASE_URL="https://gitea.example.com"
GITEA_ACCESS_TOKEN="access_token"
GITEA_OWNER="owner"
GITEA_REPOSITORY="repository"
GITEA_PACKAGE_REGISTRY="composer"

View File

@@ -1,5 +1,7 @@
PHP_VERSION="8.3"
GITEA_INSTANCE_BASE_URL="https://gitea.example.com"
GITEA_ACCESS_TOKEN="access_token"
GITEA_OWNER="owner"
GITEA_REPOSITORY="repository"
GITEA_PACKAGE_REGISTRY="composer"
GITEA_PACKAGE_REGISTRY="composer"

26
.gitattributes vendored Normal file
View File

@@ -0,0 +1,26 @@
* text=auto
AUTHORS text
CHANGELOG text
CHANGES text
CONTRIBUTING text
COPYING text
LICENSE text
NEWS text
.editorconfig text
.gitattributes text
.gitconfig text
.env text
.env.example text
.env.ci text
.gitignore text
.dockerignore text
Dockerfile text
Makefile text
composer.json text eol=lf
*.md text diff=markdown
*.php text diff=php
*.sh text eol=lf
*.yml text
*.json text

197
.github/workflows/tests.yml vendored Normal file
View File

@@ -0,0 +1,197 @@
name: tests
on:
pull_request: ~
push:
branches: ['*.*.*']
env:
BRANCH: ${{ github.head_ref || github.ref_name }}
jobs:
docker-build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
ref: ${{env.BRANCH}}
- run: cp .env.dist .env
- uses: falti/dotenv-action@v1
id: dotenv
- uses: docker/setup-buildx-action@v3
- uses: docker/build-push-action@v6
with:
context: .
file: ./Dockerfile
build-args: |
PHP_VERSION=${{ steps.dotenv.outputs.PHP_VERSION }}
push: false
composer-validate:
runs-on: ubuntu-latest
strategy:
matrix:
php:
- 8.3
steps:
- uses: actions/checkout@v4
with:
ref: ${{env.BRANCH}}
- uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
tools: composer:v2
coverage: none
- run: composer validate
composer-normalize:
runs-on: ubuntu-latest
strategy:
matrix:
php:
- 8.3
steps:
- uses: actions/checkout@v4
with:
ref: ${{env.BRANCH}}
- uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
tools: composer:v2
coverage: none
- uses: ramsey/composer-install@v3
with:
composer-options: --optimize-autoloader
- run: composer normalize
composer-require-checker:
runs-on: ubuntu-latest
strategy:
matrix:
php:
- 8.3
steps:
- uses: actions/checkout@v4
with:
ref: ${{env.BRANCH}}
- uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
tools: composer:v2
coverage: none
- uses: ramsey/composer-install@v3
with:
composer-options: --optimize-autoloader
- run: composer require-checker
composer-unused:
runs-on: ubuntu-latest
strategy:
matrix:
php:
- 8.3
steps:
- uses: actions/checkout@v4
with:
ref: ${{env.BRANCH}}
- uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
tools: composer:v2
coverage: none
- uses: ramsey/composer-install@v3
with:
composer-options: --optimize-autoloader
- run: composer unused
linter:
runs-on: ubuntu-latest
strategy:
matrix:
php:
- 8.3
steps:
- uses: actions/checkout@v4
with:
ref: ${{env.BRANCH}}
- uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
tools: composer:v2, cs2pr
coverage: none
- uses: ramsey/composer-install@v3
with:
composer-options: --optimize-autoloader
- run: composer php-cs-fixer -- --dry-run --diff --format=checkstyle --ansi | cs2pr
refactoring:
runs-on: ubuntu-latest
strategy:
matrix:
php:
- 8.3
steps:
- uses: actions/checkout@v4
with:
ref: ${{env.BRANCH}}
- uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
tools: composer:v2
coverage: none
- uses: ramsey/composer-install@v3
with:
composer-options: --optimize-autoloader
- run: composer rector -- --dry-run
static-analysis:
runs-on: ubuntu-latest
strategy:
matrix:
php:
- 8.3
steps:
- uses: actions/checkout@v4
with:
ref: ${{env.BRANCH}}
- uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
tools: composer:v2
coverage: none
- uses: ramsey/composer-install@v3
with:
composer-options: --optimize-autoloader
- run: composer psalm -- --php-version=${{ matrix.php }} --stats --output-format=github --shepherd
unit-tests:
runs-on: ubuntu-latest
strategy:
matrix:
php:
- 8.3
steps:
- uses: actions/checkout@v4
with:
ref: ${{env.BRANCH}}
- uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
tools: composer:v2
coverage: pcov
- uses: ramsey/composer-install@v3
with:
composer-options: --optimize-autoloader
# - run: composer test -- --colors=always --order-by=random --coverage-clover coverage.xml
- run: composer phpunit
mutation-tests:
runs-on: ubuntu-latest
strategy:
matrix:
php:
- 8.3
steps:
- uses: actions/checkout@v4
with:
ref: ${{env.BRANCH}}
- uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
tools: composer:v2
coverage: pcov
- uses: ramsey/composer-install@v3
with:
composer-options: --optimize-autoloader
- run: composer infection

3
.gitignore vendored
View File

@@ -1,2 +1,3 @@
.idea
/.env
.env
vendor

27
.php-cs-fixer.dist.php Normal file
View File

@@ -0,0 +1,27 @@
<?php
declare(strict_types=1);
use PhpCsFixer\Config;
use PhpCsFixer\Finder;
use PHPyh\CodingStandard\PhpCsFixerCodingStandard;
$finder = Finder::create()
->in([
__DIR__ . '/src',
__DIR__ . '/tests',
])
->append([
__FILE__,
])
->notPath([
'benchmark.php',
]);
$config = (new Config())
->setFinder($finder)
->setCacheFile(__DIR__ . '/var/.php-cs-fixer.cache');
(new PhpCsFixerCodingStandard())->applyTo($config);
return $config;

View File

@@ -1,9 +1,30 @@
FROM cybercinch/base-alpine-bash:latest
ARG PHP_VERSION=8.3
COPY scripts/docker-entrypoint.sh /entrypoint.sh
RUN chmod a+x /entrypoint.sh
FROM rosven9856/php:$PHP_VERSION
ENV upload_file="build/Package.zip"
ENV overwrite_files="false"
RUN apk add --update --no-cache --virtual .build-deps ${PHPIZE_DEPS} \
&& pecl install pcov \
&& docker-php-ext-enable pcov \
&& apk del .build-deps
CMD ["/entrypoint.sh"]
RUN addgroup -g 1000 --system php
RUN adduser -G php --system -D -s /bin/sh -u 1000 php
RUN chown php:php /home/php
RUN chown php:php /usr/local/bin/composer
RUN mkdir /usr/bin/app
RUN chown -R php:php /usr/bin/app
COPY . /usr/bin/app
WORKDIR /usr/bin/app
ENV GITHUB_WORKSPACE=/usr/bin/app
RUN composer install
VOLUME ["/usr/bin/app"]
USER php
CMD ["php", "-f", "/usr/bin/app/app.php"]

View File

@@ -2,7 +2,7 @@
<img width="560" height="260" src="docs/image/github_gitea_actions.jpg" alt="github gitea actions">
</p>
# Add/Update a Composer package in Gitea
# Updating a package in the Gitea system using GitHub Actions
[![License](https://img.shields.io/github/license/rosven9856/gitea-package-action)](https://github.com/rosven9856/gitea-package-action/blob/master/LICENSE)
@@ -13,10 +13,34 @@ This action will update the package version in the Gitea system using the API an
```yaml
steps:
- uses: https://hub.cybercinch.nz/gitea-composer-upload@main
- uses: rosven9856/gitea-package-action@0.1.0
with:
base_url: "https://gitea_instance_url"
access_token: "${{ secrets._G_TOKEN }}"
username: "${{ secrets._G_USERNAME }}"
owner: "owner"
package_version: "${{ env.GITHUB_REF_NAME }}"
gitea_instance_base_url: "https://gitea_instance_url"
gitea_access_token: "${{ secrets._GITEA_ACCESS_TOKEN }}"
gitea_owner: "owner"
gitea_repository: "repository"
gitea_package_registry: "composer"
```
## Developing
build
```shell
docker build . --build-arg=PHP_VERSION=8.3 -t=gitea-package-action
```
initialization
```shell
docker run --rm -v .:/usr/bin/app gitea-package-action composer install
```
running
```shell
docker run --rm -v .:/usr/bin/app gitea-package-action php app.php
```
testing
```shell
docker run --rm -v .:/usr/bin/app gitea-package-action composer tests
```

12
action.Dockerfile Normal file
View File

@@ -0,0 +1,12 @@
ARG PHP_VERSION=8.3
FROM rosven9856/php:$PHP_VERSION
COPY . /usr/bin/app
WORKDIR /usr/bin/app
RUN composer install --optimize-autoloader
VOLUME ["/usr/bin/app"]
ENTRYPOINT ["php", "-f", "/usr/bin/app/app.php"]

View File

@@ -1,39 +1,30 @@
name: "Gitea Composer upload package"
description: "Updating a composer package in the Gitea system using GitHub Actions"
name: "Gitea updating package"
description: "Updating a package in the Gitea system using GitHub Actions"
inputs:
baseurl:
description: "Base URL for Gitea Instance"
gitea_instance_base_url:
description: "gitea instance base url"
required: true
username:
description: "Username"
gitea_access_token:
description: "gitea access token"
required: true
access_token:
description: "Access Token"
gitea_owner:
description: "gitea owner"
required: true
owner:
description: "Package owner"
required: false
version:
description: "Version of package"
gitea_repository:
description: "gitea repository"
required: true
gitea_package_registry:
description: "gitea package registry"
required: true
overwrite_files:
description: "Overwrite package if already exists"
required: false
default: "false"
upload_file:
description: "The file path to upload"
required: false
default: "build/Package.zip"
runs:
using: "docker"
image: "Dockerfile"
env:
baseurl: ${{ inputs.baseurl }}
username: ${{ inputs.username }}
access_token: ${{ inputs.access_token }}
version: ${{ inputs.version }}
overwrite_files: ${{ inputs.overwrite_files }}
image: "action.Dockerfile"
args:
- ${{ inputs.gitea_instance_base_url }}
- ${{ inputs.gitea_access_token }}
- ${{ inputs.gitea_owner }}
- ${{ inputs.gitea_repository }}
- ${{ inputs.gitea_package_registry }}
branding:
icon: 'package'
color: 'green'
color: 'green'

252
app.php Normal file
View File

@@ -0,0 +1,252 @@
<?php
declare(strict_types=1);
require __DIR__ . '/vendor/autoload.php';
use App\Action;
use App\Configuration\Configuration;
use App\Casting\MapperType;
\define('RED', "\033[0;31m");
\define('GREEN', "\033[1;32m");
\define('YELLOW', "\033[1;33m");
\define('LITE_CYAN', "\e[96m");
\define('NC', "\033[0m");
try {
$configuration = (new Configuration())
->set('base_url', \getenv('gitea_instance_base_url'))
->set('access_token', \getenv('gitea_access_token'))
->set('owner', \getenv('gitea_owner'))
->set('owner', \getenv('gitea_owner'))
->set('repository', \getenv('gitea_repository'))
->set('package_registry', \getenv('gitea_package_registry'))
;
(new Action($configuration))->run();
} catch (Exception) {
// echo 'Error: ' . $e->getMessage() . "\n" . 'Trace: ' . $e->getTraceAsString();
}
function sendRequest ($method = 'GET', $endpoint = '', $data = []): array {
if (!\extension_loaded('curl')) {
throw new \Exception('CURL extension is not loaded');
}
$curl = \curl_init();
if(!$curl) {
throw new \Exception('CURL extension is not loaded');
}
$payload = '';
if (isset($data['request']) && \is_array($data['request']) && \count($data['request']) > 0) {
$payload = \json_encode($data['request']);
}
$fh = null;
$fileSize = 0;
if (isset($data['file']) && !empty($data['file']) && \file_exists($data['file'])) {
$fh = \fopen($data['file'], 'r');
$fileSize = \filesize($data['file']);
}
if (isset($data['user']) && !empty($data['user'])) {
\curl_setopt($curl, CURLOPT_USERPWD, $data['user'] . ':' . \getenv('gitea_access_token'));
} else {
$endpoint .= '?access_token=' . \getenv('gitea_access_token');
}
\curl_setopt($curl, CURLOPT_URL, \getenv('gitea_instance_base_url') . $endpoint /* . '?access_token=' . \getenv('gitea_access_token')*/);
\curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 2);
\curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, true);
\curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
\curl_setopt($curl, CURLOPT_HEADER, true);
if ($method === 'POST' || $method === 'PUT') {
if (!empty($payload)) {
\curl_setopt($curl, CURLOPT_POSTFIELDS, $payload);
\curl_setopt($curl, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
}
}
if ($method === 'PUT') {
\curl_setopt($curl, CURLOPT_PUT, true);
}
if ($fh) {
\curl_setopt($curl, CURLOPT_INFILE, $fh);
\curl_setopt($curl, CURLOPT_INFILESIZE, $fileSize);
}
\curl_setopt($curl, CURLOPT_CONNECTTIMEOUT, 20);
\curl_setopt($curl, CURLOPT_TIMEOUT, 20);
$response = \curl_exec($curl);
if(\curl_errno($curl) > 0) {
throw new \Exception('Curl error: #' . \curl_errno($curl) . ' - ' . \curl_error($curl));
}
$header = \substr($response, 0, \curl_getinfo($curl, CURLINFO_HEADER_SIZE));
$body = \substr($response, \curl_getinfo($curl, CURLINFO_HEADER_SIZE));
$httpCode = (int) \curl_getinfo($curl, CURLINFO_HTTP_CODE);
$contentType = \curl_getinfo($curl, CURLINFO_CONTENT_TYPE);
$headers = \explode("\r\n", $header);
\curl_close($curl);
if (isset($data['file']) && !empty($data['file']) && \file_exists($data['file'])) {
\fclose($fh);
}
return [
'http_code' => $httpCode,
'content_type' => $contentType,
'headers' => $headers,
'body' => $body,
];
}
function responseEncode (array $response): mixed {
$data = [];
if ($response['content_type'] !== 'application/json;charset=utf-8') {
return $data;
}
$data = \json_decode($response['body'], true);
if (\json_last_error() !== JSON_ERROR_NONE) {
throw new \Exception('Invalid response json: ' . json_last_error_msg());
}
return $data;
}
function showTerminalMessage (string $message = '', string $color = ''): void
{
echo $color . $message . NC . "\r\n";
}
try {
if (empty(\getenv('gitea_instance_base_url'))) {
throw new \Exception('gitea_instance_base_url empty');
}
if (empty(\getenv('gitea_access_token'))) {
throw new \Exception('gitea_access_token empty');
}
if (empty(\getenv('gitea_owner'))) {
throw new \Exception('gitea_owner empty');
}
if (empty(\getenv('gitea_repository'))) {
throw new \Exception('gitea_repository empty');
}
if (empty(\getenv('gitea_package_registry'))) {
throw new \Exception('gitea_package_registry empty');
}
if (!\in_array(\getenv('gitea_package_registry'), ['composer'])) {
throw new \Exception('Package registry {' . \getenv('gitea_package_registry') . '} is not supported');
}
$response = sendRequest('GET', '/api/v1/user');
if ($response['http_code'] !== 200) {
throw new \Exception('Failed to get user information. Access denied. Response http code: ' . $response['http_code']);
}
$data = responseEncode($response);
if ($response['http_code'] !== 200) {
throw new \Exception('Failed to get user information. Response http code: ' . $response['http_code'] . ', Message: ' . $data['message']);
}
$user = $data;
$login = $user['login'];
showTerminalMessage('User data: OK', GREEN);
$response = sendRequest('GET', '/api/v1/repos/' . \getenv('gitea_owner') . '/' . \getenv('gitea_repository') . '/releases', [
'user' => $login,
]);
if ($response['http_code'] !== 200) {
throw new \Exception('Failed to get repository releases information. Access denied. Response http code: ' . $response['http_code']);
}
$data = responseEncode($response);
if ($response['http_code'] !== 200) {
throw new \Exception('Failed to get repository releases information. Response http code: ' . $response['http_code'] . ', Message: ' . $data['message']);
}
if (!isset($data[0]) || !\is_array($data[0])) {
throw new \Exception('Unexpected release data structure');
}
$lastRelease = $data[0];
$tag = $lastRelease['tag_name'];
showTerminalMessage('Last release data: OK', GREEN);
$response = sendRequest('GET', '/api/v1/repos/' . \getenv('gitea_owner') . '/' . \getenv('gitea_repository') . '/archive/' . $tag . '.zip', [
'user' => $login,
]);
$zipContent = $response['body'];
if ($response['http_code'] !== 200) {
throw new \Exception('Failed receiving zip archive. Response http code: ' . $response['http_code']);
}
if (empty($response['body'])) {
throw new \Exception('Failed receiving zip archive. Empty file');
}
\file_put_contents(__DIR__ . '/package.zip', $zipContent);
showTerminalMessage('Download zip archive: OK', GREEN);
$response = sendRequest('PUT', '/api/packages/' . \getenv('gitea_owner') . '/composer?version=' . $tag, [
'user' => $login,
'file' => __DIR__ . '/package.zip',
]);
\unlink(__DIR__ . '/package.zip');
if ($response['http_code'] !== 201) {
$data = responseEncode($response);
throw new \Exception('Failed update package. Response http code: ' . $response['http_code'] . ', Message: ' . $data['errors'][0]['message']);
}
showTerminalMessage('Update package: OK', GREEN);
} catch (\Exception $e) {
showTerminalMessage("\r\n");
showTerminalMessage( 'FAILED!', RED);
showTerminalMessage( "Error: " . $e->getMessage(), RED);
exit(1);
}
showTerminalMessage("\r\n");
showTerminalMessage('SUCCESS!', GREEN);

View File

@@ -1,17 +0,0 @@
services:
composer-uploader:
container_name: compose-upload-gitea
build:
context: ./
dockerfile: Dockerfile
environment:
baseurl: ${base_url}
access_token: ${access_token}
username: ${username}
owner: ${owner}
version: ${version}
repo_name: ${repo_name}
overwrite_files: true
working_dir: /
volumes:
- ./build:/build

View File

@@ -0,0 +1,16 @@
{
"symbol-whitelist" : [],
"php-core-extensions" : [
"Core",
"date",
"json",
"hash",
"pcre",
"Phar",
"Reflection",
"SPL",
"random",
"standard"
],
"scan-files" : []
}

68
composer.json Normal file
View File

@@ -0,0 +1,68 @@
{
"name": "rosven9856/gitea-package-action",
"description": "",
"license": "MIT",
"authors": [
{
"name": "rosven9856",
"email": "rosven9856@gmail.com"
}
],
"require": {
"php": "^8.3",
"ext-curl": "*"
},
"require-dev": {
"ergebnis/composer-normalize": "^2.42",
"friendsofphp/php-cs-fixer": "^3.57",
"icanhazstring/composer-unused": "^0.8.11",
"infection/infection": "^0.27.11",
"maglnet/composer-require-checker": "^4.11",
"phpunit/phpunit": "^10.4.2",
"phpyh/coding-standard": "^2.6",
"psalm/plugin-phpunit": "^0.18.4",
"rector/rector": "^1.1",
"symfony/var-dumper": "^7.1",
"vimeo/psalm": "^5.24"
},
"autoload": {
"psr-4": {
"App\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"App\\": "tests/"
}
},
"config": {
"allow-plugins": {
"ergebnis/composer-normalize": true,
"infection/extension-installer": true
},
"sort-packages": true,
"sort-scripts": false
},
"scripts": {
"infection": "infection --threads=max --show-mutations",
"normalize": "composer normalize --dry-run --diff --ansi",
"php-cs-fixer": "php-cs-fixer fix --diff --verbose",
"phpunit": "phpunit --colors=always --order-by=random",
"psalm": "psalm --show-info=true --no-diff",
"rector": "rector process",
"require-checker": "composer-require-checker check --config-file=composer-require-checker.json composer.json",
"tests": [
"@validate",
"@normalize",
"@require-checker",
"@unused",
"composer php-cs-fixer -- --dry-run --diff --format=checkstyle --ansi",
"composer rector -- --dry-run",
"composer psalm -- --stats --shepherd",
"@phpunit",
"@infection"
],
"unused": "composer-unused --excludePackage=ext-curl",
"validate": "composer validate --strict --ansi"
}
}

7801
composer.lock generated Normal file

File diff suppressed because it is too large Load Diff

19
infection.json5.dist Normal file
View File

@@ -0,0 +1,19 @@
{
"$schema": "vendor/infection/infection/resources/schema.json",
"source": {
"directories": [
"src"
]
},
"logs": {
"text": "var/infection.log",
"stryker": {
"report": "/^\\d+\\.\\d+\\.x$/"
},
},
"tmpDir": "var",
"minCoveredMsi": 20,
"mutators": {
"@default": true
}
}

27
phpunit.xml.dist Normal file
View File

@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
cacheDirectory="var/phpunit"
executionOrder="depends,defects"
requireCoverageMetadata="true"
beStrictAboutOutputDuringTests="true"
failOnRisky="true"
failOnWarning="true"
>
<php>
<ini name="display_errors" value="1"/>
<ini name="error_reporting" value="-1"/>
</php>
<testsuites>
<testsuite name="default">
<directory>tests</directory>
</testsuite>
</testsuites>
<source restrictDeprecations="true" restrictNotices="true" restrictWarnings="true">
<include>
<directory>src</directory>
</include>
</source>
</phpunit>

75
psalm.xml.dist Normal file
View File

@@ -0,0 +1,75 @@
<?xml version="1.0"?>
<psalm
cacheDirectory="var/psalm"
checkForThrowsDocblock="true"
checkForThrowsInGlobalScope="true"
disableSuppressAll="true"
ensureArrayStringOffsetsExist="true"
errorLevel="1"
findUnusedBaselineEntry="true"
findUnusedCode="true"
findUnusedPsalmSuppress="true"
findUnusedVariablesAndParams="true"
memoizeMethodCallResults="true"
reportMixedIssues="true"
sealAllMethods="true"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="https://getpsalm.org/schema/config"
xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd"
>
<enableExtensions>
<extension name="dom"/>
</enableExtensions>
<plugins>
<pluginClass class="Psalm\PhpUnitPlugin\Plugin"/>
</plugins>
<projectFiles>
<file name="app.php"/>
<directory name="src"/>
<directory name="tests"/>
<ignoreFiles>
<directory name="var"/>
<directory name="vendor"/>
<file name="tests/benchmark.php"/>
</ignoreFiles>
</projectFiles>
<ignoreExceptions>
<classAndDescendants name="LogicException"/>
<classAndDescendants name="RuntimeException"/>
<classAndDescendants name="ReflectionException"/>
</ignoreExceptions>
<issueHandlers>
<MissingThrowsDocblock>
<errorLevel type="suppress">
<directory name="tests"/>
</errorLevel>
</MissingThrowsDocblock>
<MixedAssignment errorLevel="suppress"/>
<PossiblyUnusedMethod>
<errorLevel type="suppress">
<directory name="tests"/>
</errorLevel>
</PossiblyUnusedMethod>
<UnusedMethodCall>
<errorLevel type="suppress">
<directory name="tests"/>
</errorLevel>
</UnusedMethodCall>
</issueHandlers>
<forbiddenFunctions>
<function name="dd"/>
<function name="die"/>
<function name="dump"/>
<function name="echo"/>
<function name="eval"/>
<function name="exit"/>
<function name="print"/>
<function name="sleep"/>
<function name="usleep"/>
</forbiddenFunctions>
</psalm>

20
rector.php Normal file
View File

@@ -0,0 +1,20 @@
<?php
declare(strict_types=1);
use Rector\Config\RectorConfig;
use Rector\Set\ValueObject\LevelSetList;
use Rector\Set\ValueObject\DowngradeLevelSetList;
return static function (RectorConfig $rectorConfig): void {
$rectorConfig->parallel();
$rectorConfig->cacheDirectory(__DIR__ . '/var/rector');
$rectorConfig->paths([
__DIR__ . '/src',
__DIR__ . '/tests',
]);
$rectorConfig->sets([
LevelSetList::UP_TO_PHP_83,
// DowngradeLevelSetList::DOWN_TO_PHP_81,
]);
};

View File

@@ -1,143 +0,0 @@
#!/bin/bash
# This is to serve as a Plugin for Drone to enable uploading of generic packages to Gitea e.g Ansible Roles
set -eo pipefail
shopt -s nullglob
# check to see if this file is being run or sourced from another script
_is_sourced() {
# https://unix.stackexchange.com/a/215279
[ "${#FUNCNAME[@]}" -ge 2 ] &&
[ "${FUNCNAME[0]}" = '_is_sourced' ] &&
[ "${FUNCNAME[1]}" = 'source' ]
}
# logging functions
action_log() {
local type="$1"
shift
# accept argument string or stdin
local text="$*"
if [ "$#" -eq 0 ]; then text="$(cat)"; fi
local dt
dt="$(date -D 'YYYY-MM-DD hh:mm[:ss]')"
printf '%s [%s] [gitea-composer-uploader]: %s\n' "$dt" "$type" "$text"
}
action_note() {
action_log INF "$@"
}
action_warn() {
action_log WRN "$@" >&2
}
action_error() {
action_log ERR "$@" >&2
}
# Verify that the minimally required password settings are set for operation.
function verify_minimum_env {
if [ -z "$username" ]; then
action_warn "username is required for action operation"
fi
if [ -z "$baseurl" ]; then
action_warn "baseurl setting is required for action operation"
fi
if [ -z "$owner" ]; then
action_warn "owner setting is required for action operation"
fi
if [ -z "$access_token" ]; then
action_warn "access_token setting is required for action operation"
fi
if [ -z "$version" ]; then
action_warn "version setting is required for action operation"
fi
if [ -z "$username" ] ||
[ -z "$baseurl" ] ||
[ -z "$version" ] ||
[ -z "$access_token" ]; then
action_error <<-'EOF'
You need to specify one/all of the following settings:
- username
- access_token
- baseurl
EOF
exit 1
fi
action_note "Sufficient configuration"
}
function delete_file {
no_prefix_version="${version##v}"
username_or_owner=${owner:-$username}
no_owner_repo_name="${GITHUB_REPOSITORY##"$username_or_owner"/}"
# shellcheck disable=SC2154
for file in ${upload_file}; do
action_note "curl -s -o /dev/null -w '%{http_code}' --user \"${username}:${access_token}\" -X DELETE ${baseurl}/api/v1/packages/${owner:-$username}/composer/${owner:-$username}%2F${no_owner_repo_name}/${no_prefix_version}"
response=$(curl -s -o /dev/null -w "%{http_code}" --user "${username}:${access_token}" -X DELETE "${baseurl}/api/v1/packages/${owner:-$username}/composer/${owner:-$username}%2F${no_owner_repo_name}/${no_prefix_version}")
if [ "${response}" == 204 ] || [ "${response}" == 200 ]; then
action_note "Deleted package version ${version} for ${owner:-$username}/${repo_name}"
elif [ "${response}" == 404 ]; then
action_error "Not Found: Odd I cannot locate your package! [bug]"
else
action_error "Response code was ${response}"
fi
done
}
function process_upload_file {
for file in ${upload_file}; do
#action_note "curl -s -o /dev/null -w '%{http_code}' --user \"${username}:${access_token}\" --upload-file ${file} \"${baseurl}/api/packages/${owner:-$username}/composer?version=${version}\""
response=$(curl -s -o /dev/null -w "%{http_code}" --user "${username}:${access_token}" --upload-file "${file}" "${baseurl}/api/packages/${owner:-$username}/composer?version=${version}")
if [ "${response}" == 409 ]; then
action_error "Conflict: File already exists"
if [ "${FILE_OVERWRITE:=$overwrite_files}" == 'true' ]; then
# Delete file as already exists
delete_file
response=$(curl -s -o /dev/null -w "%{http_code}" --user "${username}:${access_token}" --upload-file "${file}" "${baseurl}/api/packages/${owner:-$username}/composer?version=${version}")
if [ "${response}" == 409 ]; then
action_error "Conflict: File already exists"
elif [ "${response}" == 201 ]; then
action_note "File uploaded successfully"
elif [ "${response}" == 400 ]; then
action_error "Bad Request: Version likely already exists"
fi
else
action_error 'Unable to upload file. Maybe toggle overwrite_files setting to true :)'
exit 1
fi
elif [ "${response}" == 201 ]; then
action_note "File uploaded successfully"
elif [ "${response}" == 400 ]; then
action_warn "Bad Request: Version likely already exists"
if [ "${FILE_OVERWRITE:=$overwrite_files}" == 'true' ]; then
# Delete file as already exists
delete_file
response=$(curl -s -o /dev/null -w "%{http_code}" --user "${username}:${access_token}" --upload-file "${file}" "${baseurl}/api/packages/${owner:-$username}/composer?version=${version}")
if [ "${response}" == 409 ]; then
action_error "Conflict: File already exists"
elif [ "${response}" == 201 ]; then
action_note "File uploaded successfully"
elif [ "${response}" == 400 ]; then
action_error "Bad Request: Version likely already exists"
exit 1
fi
else
action_error 'Unable to upload file. Maybe toggle overwrite_files setting to true :)'
exit 1
fi
fi
done
}
_main() {
action_note "Starting"
verify_minimum_env "$@"
process_upload_file "$@"
}
# If we are sourced from elsewhere, don't perform any further actions
if ! _is_sourced; then
_main "$@"
fi

0
src/.gitkeep Normal file
View File

37
src/Action.php Normal file
View File

@@ -0,0 +1,37 @@
<?php
declare(strict_types=1);
namespace App;
use App\Configuration\Configuration;
final class Action
{
private Configuration $configuration;
public function __construct(Configuration $configuration)
{
$this->configuration = $configuration;
return $this;
}
public function run(): void
{
$this->checkExtensions();
$this->checkConfiguration();
// $this->configuration->getCastingType('param', MapperType::STRING)->getAsCasting()
}
private function checkExtensions(): void
{
}
private function checkConfiguration(): void
{
}
}

10
src/Casting/Casting.php Normal file
View File

@@ -0,0 +1,10 @@
<?php
declare(strict_types=1);
namespace App\Casting;
interface Casting
{
public function getAsCasting(): mixed;
}

View File

@@ -0,0 +1,46 @@
<?php
declare(strict_types=1);
namespace App\Casting;
use App\Casting\Type\IntegerType;
use App\Casting\Type\StringType;
use App\Casting\Type\Type;
use App\Exception\Casting\TypeNotFountException;
final readonly class MapperType
{
public const string STRING = 'string';
public const string INTEGER = 'integer';
public const string INT = 'int';
private const array MAP = [
self::STRING => StringType::class,
self::INTEGER => IntegerType::class,
self::INT => IntegerType::class,
];
private string $class;
/**
* @throws TypeNotFountException
*/
public function __construct(string $type)
{
if (!isset(self::MAP[$type])) {
throw new TypeNotFountException(
\sprintf('Casting type "%s" is not found.', $type),
);
}
$this->class = self::MAP[$type];
}
/**
* @return mixed
*/
public function create(mixed $value): Type
{
return new $this->class($value);
}
}

View File

@@ -0,0 +1,21 @@
<?php
declare(strict_types=1);
namespace App\Casting\Type;
use App\Casting\Casting;
abstract class AbstractType implements Type, Casting
{
public function __construct(protected mixed $value) {}
#[\Override]
final public function getValue()
{
return $this->value;
}
#[\Override]
abstract public function getAsCasting(): mixed;
}

View File

@@ -0,0 +1,14 @@
<?php
declare(strict_types=1);
namespace App\Casting\Type;
final class IntegerType extends AbstractType
{
#[\Override]
public function getAsCasting(): int
{
return (int) $this->getValue();
}
}

View File

@@ -0,0 +1,14 @@
<?php
declare(strict_types=1);
namespace App\Casting\Type;
final class StringType extends AbstractType
{
#[\Override]
public function getAsCasting(): string
{
return (string) $this->getValue();
}
}

10
src/Casting/Type/Type.php Normal file
View File

@@ -0,0 +1,10 @@
<?php
declare(strict_types=1);
namespace App\Casting\Type;
interface Type
{
public function getValue();
}

View File

@@ -0,0 +1,39 @@
<?php
declare(strict_types=1);
namespace App\Configuration;
use App\Casting\MapperType;
use App\Casting\Type\Type;
use App\Exception\Casting\TypeNotFountException;
final class Configuration
{
public array $options = [];
public function __construct()
{
return $this;
}
public function set(string $key, mixed $value): self
{
$this->options[$key] = $value;
return $this;
}
public function get(string $key): mixed
{
return $this->options[$key] ?? null;
}
/**
* @throws TypeNotFountException
*/
public function getCastingType(string $key, string $type): Type
{
return (new MapperType($type))->create($this->get($key));
}
}

View File

@@ -0,0 +1,9 @@
<?php
declare(strict_types=1);
namespace App\Exception\Casting;
use App\Exception\Exception;
final class TypeNotFountException extends Exception {}

View File

@@ -0,0 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Exception;
class Exception extends \Exception {}

View File

@@ -0,0 +1,52 @@
<?php
declare(strict_types=1);
namespace App\Casting;
use App\Casting\Type\IntegerType;
use App\Casting\Type\StringType;
use App\Exception\Casting\TypeNotFountException;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\TestCase;
#[CoversClass(MapperType::class)]
final class MapperTypeTest extends TestCase
{
public function testGetStringType(): void
{
$type = (new MapperType(MapperType::STRING))->create('value');
self::assertInstanceOf(
StringType::class,
$type,
);
}
public function testGetIntegerType(): void
{
$type = (new MapperType(MapperType::INTEGER))->create(1);
self::assertInstanceOf(
IntegerType::class,
$type,
);
}
public function testGetIntType(): void
{
$type = (new MapperType(MapperType::INT))->create(1);
self::assertInstanceOf(
IntegerType::class,
$type,
);
}
public function testTypeNotFountException(): void
{
self::expectException(TypeNotFountException::class);
(new MapperType('type-exception'))->create('value');
}
}

View File

@@ -0,0 +1,23 @@
<?php
declare(strict_types=1);
namespace App\Configuration;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\TestCase;
#[CoversClass(Configuration::class)]
final class ConfigurationTest extends TestCase
{
public function testUseConfiguration(): void
{
$configuration = new Configuration();
$configuration->set('test-option', 'test-value');
self::assertEquals(
$configuration->get('test-option'),
'test-value',
);
}
}

1
tests/benchmark.php Normal file
View File

@@ -0,0 +1 @@
<?php

2
var/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
*
!.gitignore