feat : numéro de version et config CI/CD
Some checks failed
Auto Tag Develop / tag (push) Failing after 4s
Some checks failed
Auto Tag Develop / tag (push) Failing after 4s
This commit is contained in:
23
.gitea/PULL_REQUEST_TEMPLATE.md
Normal file
23
.gitea/PULL_REQUEST_TEMPLATE.md
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
---
|
||||||
|
|
||||||
|
name: "Merge Request"
|
||||||
|
about: "Template de MR"
|
||||||
|
title: "[#NUMERO_TICKET] TITRE TICKET"
|
||||||
|
ref: "main"
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
| Numéro du ticket | Titre du ticket |
|
||||||
|
|------------------|-----------------|
|
||||||
|
| | |
|
||||||
|
|
||||||
|
## Description de la PR
|
||||||
|
|
||||||
|
## Modification du .env
|
||||||
|
|
||||||
|
## Check list
|
||||||
|
|
||||||
|
- [ ] Pas de régression
|
||||||
|
- [ ] TU/TI/TF rédigée
|
||||||
|
- [ ] TU/TI/TF OK
|
||||||
|
- [ ] CHANGELOG modifié
|
||||||
65
.gitea/workflows/auto-tag-develop.yml
Normal file
65
.gitea/workflows/auto-tag-develop.yml
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
name: Auto Tag Develop
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- develop
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
tag:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
token: ${{ secrets.RELEASE_TOKEN }}
|
||||||
|
persist-credentials: true
|
||||||
|
|
||||||
|
- name: Create next tag from config/version.yaml
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# Skip if current commit already has a vX.Y.Z tag
|
||||||
|
if git tag --points-at HEAD | grep -qE '^v[0-9]+\.[0-9]+\.[0-9]+$'; then
|
||||||
|
echo "Tag already exists on this commit. Skipping."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
changed_version=false
|
||||||
|
if git diff --name-only "${{ gitea.event.before }}" "${{ gitea.event.after }}" | grep -q '^config/version\.yaml$'; then
|
||||||
|
changed_version=true
|
||||||
|
fi
|
||||||
|
|
||||||
|
read_version() {
|
||||||
|
awk -F': *' '/app\.version:/{print $2}' config/version.yaml | tr -d '[:space:]' | tr -d "'\""
|
||||||
|
}
|
||||||
|
|
||||||
|
if $changed_version; then
|
||||||
|
version="$(read_version)"
|
||||||
|
if ! [[ "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
||||||
|
echo "Invalid version in version.yaml: $version" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
last_tag="$(git tag -l 'v*' --sort=-v:refname | head -n1 || true)"
|
||||||
|
if [ -z "$last_tag" ]; then
|
||||||
|
version="0.1.0"
|
||||||
|
else
|
||||||
|
base="${last_tag#v}"
|
||||||
|
IFS='.' read -r major minor patch <<< "$base"
|
||||||
|
version="${major}.${minor}.$((patch + 1))"
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf "parameters:\\n app.version: '%s'\\n" "$version" > config/version.yaml
|
||||||
|
git config user.name "gitea-actions"
|
||||||
|
git config user.email "gitea-actions@local"
|
||||||
|
git add config/version.yaml
|
||||||
|
git commit -m "chore: bump version to v$version" || true
|
||||||
|
git push origin develop || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
tag="v$version"
|
||||||
|
git tag "$tag"
|
||||||
|
git push origin "$tag"
|
||||||
65
.gitea/workflows/release-artefact.yml
Normal file
65
.gitea/workflows/release-artefact.yml
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
name: Build Release Artefact
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- "v0.0.*"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
persist-credentials: false
|
||||||
|
|
||||||
|
- name: Setup PHP
|
||||||
|
uses: shivammathur/setup-php@v2
|
||||||
|
with:
|
||||||
|
php-version: "8.4"
|
||||||
|
extensions: mbstring, intl, pdo_pgsql, xml, curl, zip, gd
|
||||||
|
|
||||||
|
- name: Setup Node
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: "lts/*"
|
||||||
|
|
||||||
|
- name: Install backend deps (prod)
|
||||||
|
env:
|
||||||
|
APP_ENV: prod
|
||||||
|
APP_DEBUG: "0"
|
||||||
|
run: composer install --no-dev --optimize-autoloader --no-interaction --no-scripts
|
||||||
|
|
||||||
|
- name: Build frontend (static)
|
||||||
|
run: |
|
||||||
|
cd frontend
|
||||||
|
npm ci
|
||||||
|
CI=1 NUXT_TELEMETRY_DISABLED=1 NUXT_PUBLIC_API_BASE=/api NUXT_PUBLIC_APP_BASE=/ npm run generate
|
||||||
|
test -f .output/public/index.html
|
||||||
|
|
||||||
|
- name: Build artefact
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
mkdir -p release
|
||||||
|
tar -czf "release/sirh-${GITHUB_REF_NAME}.tar.gz" \
|
||||||
|
bin \
|
||||||
|
config \
|
||||||
|
migrations \
|
||||||
|
public \
|
||||||
|
src \
|
||||||
|
templates \
|
||||||
|
vendor \
|
||||||
|
composer.json \
|
||||||
|
composer.lock \
|
||||||
|
symfony.lock \
|
||||||
|
frontend/.output
|
||||||
|
|
||||||
|
- name: Create Release
|
||||||
|
uses: softprops/action-gh-release@v2
|
||||||
|
with:
|
||||||
|
files: release/sirh-${{ github.ref_name }}.tar.gz
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }}
|
||||||
@@ -48,6 +48,8 @@ security:
|
|||||||
access_control:
|
access_control:
|
||||||
- { path: ^/login_check, roles: PUBLIC_ACCESS }
|
- { path: ^/login_check, roles: PUBLIC_ACCESS }
|
||||||
- { path: ^/api/docs, roles: PUBLIC_ACCESS }
|
- { path: ^/api/docs, roles: PUBLIC_ACCESS }
|
||||||
|
# Version de l'application en public
|
||||||
|
- { path: ^/api/version, roles: PUBLIC_ACCESS, methods: [ GET ] }
|
||||||
- { path: ^/api, roles: IS_AUTHENTICATED_FULLY }
|
- { path: ^/api, roles: IS_AUTHENTICATED_FULLY }
|
||||||
|
|
||||||
when@test:
|
when@test:
|
||||||
|
|||||||
@@ -8,6 +8,9 @@
|
|||||||
# https://symfony.com/doc/current/best_practices.html#use-parameters-for-application-configuration
|
# https://symfony.com/doc/current/best_practices.html#use-parameters-for-application-configuration
|
||||||
parameters:
|
parameters:
|
||||||
|
|
||||||
|
imports:
|
||||||
|
- { resource: version.yaml }
|
||||||
|
|
||||||
services:
|
services:
|
||||||
# default configuration for services in *this* file
|
# default configuration for services in *this* file
|
||||||
_defaults:
|
_defaults:
|
||||||
|
|||||||
2
config/version.yaml
Normal file
2
config/version.yaml
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
parameters:
|
||||||
|
app.version: '0.0.1'
|
||||||
@@ -3,3 +3,11 @@
|
|||||||
<NuxtPage />
|
<NuxtPage />
|
||||||
</NuxtLayout>
|
</NuxtLayout>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
const { load } = useAppVersion()
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
load()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|||||||
17
frontend/composables/useAppVersion.ts
Normal file
17
frontend/composables/useAppVersion.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
export const useAppVersion = () => {
|
||||||
|
const api = useApi()
|
||||||
|
const version = useState<string | null>('app-version', () => null)
|
||||||
|
|
||||||
|
const load = async () => {
|
||||||
|
if (version.value) {
|
||||||
|
return version.value
|
||||||
|
}
|
||||||
|
const response = await api.get<{ version: string }>('version', {}, {
|
||||||
|
toast: false
|
||||||
|
})
|
||||||
|
version.value = response.version
|
||||||
|
return version.value
|
||||||
|
}
|
||||||
|
|
||||||
|
return { version, load }
|
||||||
|
}
|
||||||
@@ -5,3 +5,7 @@
|
|||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
const { version } = useAppVersion()
|
||||||
|
</script>
|
||||||
|
|||||||
@@ -43,7 +43,7 @@
|
|||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<div class="p-4">
|
<div class="flex flex-col gap-2 items-center p-4">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="w-full rounded-lg px-4 py-2 text-md font-semibold text-white bg-primary-500"
|
class="w-full rounded-lg px-4 py-2 text-md font-semibold text-white bg-primary-500"
|
||||||
@@ -51,6 +51,7 @@
|
|||||||
>
|
>
|
||||||
Déconnexion
|
Déconnexion
|
||||||
</button>
|
</button>
|
||||||
|
<p class="font-bold">v{{ version }}</p>
|
||||||
</div>
|
</div>
|
||||||
</aside>
|
</aside>
|
||||||
|
|
||||||
@@ -63,6 +64,7 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
const auth = useAuthStore()
|
const auth = useAuthStore()
|
||||||
|
const { version } = useAppVersion()
|
||||||
|
|
||||||
const handleLogout = async () => {
|
const handleLogout = async () => {
|
||||||
await auth.logout()
|
await auth.logout()
|
||||||
|
|||||||
@@ -175,7 +175,7 @@ const monthStartDate = computed(() => new Date(selectedYear.value, selectedMonth
|
|||||||
const monthEndDate = computed(() => new Date(selectedYear.value, selectedMonth.value + 1, 0))
|
const monthEndDate = computed(() => new Date(selectedYear.value, selectedMonth.value + 1, 0))
|
||||||
|
|
||||||
const gridStyle = computed(() => ({
|
const gridStyle = computed(() => ({
|
||||||
gridTemplateColumns: `220px repeat(${daysInMonth.value.length}, minmax(44px, 1fr))`
|
gridTemplateColumns: `160px repeat(${daysInMonth.value.length}, minmax(44px, 1fr))`
|
||||||
}))
|
}))
|
||||||
|
|
||||||
const form = reactive({
|
const form = reactive({
|
||||||
|
|||||||
@@ -1,72 +1,74 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="mx-auto w-full max-w-lg">
|
<div class="mx-auto w-full max-w-lg">
|
||||||
<span
|
<span
|
||||||
class="flex items-center justify-center bg-white text-xl font-bold uppercase text-primary-500 p-4"
|
class="flex items-center justify-center bg-white text-xl font-bold uppercase text-primary-500 p-4"
|
||||||
>
|
>
|
||||||
<img src="/malio.png" alt="Logo" class="w-[150px]"/>
|
<img src="/malio.png" alt="Logo" class="w-[150px]"/>
|
||||||
</span>
|
</span>
|
||||||
<form
|
<form
|
||||||
class="mt-8 space-y-6 rounded-lg border border-neutral-200 bg-white p-6 shadow-sm"
|
class="mt-8 space-y-6 rounded-lg border border-neutral-200 bg-white p-6 shadow-sm"
|
||||||
@submit.prevent="handleSubmit"
|
@submit.prevent="handleSubmit"
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<label class="text-sm font-semibold text-neutral-700" for="username">
|
<label class="text-sm font-semibold text-neutral-700" for="username">
|
||||||
Nom d'utilisateur
|
Nom d'utilisateur
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
id="username"
|
id="username"
|
||||||
v-model="username"
|
v-model="username"
|
||||||
type="text"
|
type="text"
|
||||||
autocomplete="username"
|
autocomplete="username"
|
||||||
class="mt-2 w-full rounded-md border border-neutral-300 bg-white px-3 py-2 text-base text-neutral-900 focus:border-primary-500 focus:outline-none focus:ring-2 focus:ring-primary-200"
|
class="mt-2 w-full rounded-md border border-neutral-300 bg-white px-3 py-2 text-base text-neutral-900 focus:border-primary-500 focus:outline-none focus:ring-2 focus:ring-primary-200"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label class="text-sm font-semibold text-neutral-700" for="password">
|
<label class="text-sm font-semibold text-neutral-700" for="password">
|
||||||
Mot de passe
|
Mot de passe
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
id="password"
|
id="password"
|
||||||
v-model="password"
|
v-model="password"
|
||||||
type="password"
|
type="password"
|
||||||
autocomplete="current-password"
|
autocomplete="current-password"
|
||||||
class="mt-2 w-full rounded-md border border-neutral-300 bg-white px-3 py-2 text-base text-neutral-900 focus:border-primary-500 focus:outline-none focus:ring-2 focus:ring-primary-200"
|
class="mt-2 w-full rounded-md border border-neutral-300 bg-white px-3 py-2 text-base text-neutral-900 focus:border-primary-500 focus:outline-none focus:ring-2 focus:ring-primary-200"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
class="w-full rounded-md bg-primary-500 px-4 py-2 text-base font-semibold text-white transition hover:bg-primary-600 disabled:cursor-not-allowed disabled:opacity-60"
|
class="w-full rounded-md bg-primary-500 px-4 py-2 text-base font-semibold text-white transition hover:bg-primary-600 disabled:cursor-not-allowed disabled:opacity-60"
|
||||||
:disabled="isSubmitting"
|
:disabled="isSubmitting"
|
||||||
>
|
>
|
||||||
Se connecter
|
Se connecter
|
||||||
</button>
|
</button>
|
||||||
</form>
|
<p class="font-bold">v{{ version }}</p>
|
||||||
</div>
|
</form>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
definePageMeta({ layout: 'auth' })
|
definePageMeta({layout: 'auth'})
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const auth = useAuthStore()
|
const auth = useAuthStore()
|
||||||
|
const { version } = useAppVersion()
|
||||||
|
|
||||||
const username = ref('')
|
const username = ref('')
|
||||||
const password = ref('')
|
const password = ref('')
|
||||||
const isSubmitting = ref(false)
|
const isSubmitting = ref(false)
|
||||||
|
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
if (isSubmitting.value) return
|
if (isSubmitting.value) return
|
||||||
|
|
||||||
isSubmitting.value = true
|
isSubmitting.value = true
|
||||||
try {
|
try {
|
||||||
console.log(useRuntimeConfig().public.apiBase)
|
console.log(useRuntimeConfig().public.apiBase)
|
||||||
await auth.login(username.value, password.value)
|
await auth.login(username.value, password.value)
|
||||||
|
|
||||||
await router.push('/')
|
await router.push('/')
|
||||||
} finally {
|
} finally {
|
||||||
isSubmitting.value = false
|
isSubmitting.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
25
src/ApiResource/AppVersion.php
Normal file
25
src/ApiResource/AppVersion.php
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\ApiResource;
|
||||||
|
|
||||||
|
use ApiPlatform\Metadata\ApiResource;
|
||||||
|
use ApiPlatform\Metadata\Get;
|
||||||
|
use App\State\AppVersionProvider;
|
||||||
|
use Symfony\Component\Serializer\Attribute\Groups;
|
||||||
|
|
||||||
|
#[ApiResource(
|
||||||
|
operations: [
|
||||||
|
new Get(
|
||||||
|
uriTemplate: '/version',
|
||||||
|
normalizationContext: ['groups' => ['version:read']],
|
||||||
|
provider: AppVersionProvider::class,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)]
|
||||||
|
final class AppVersion
|
||||||
|
{
|
||||||
|
#[Groups(['version:read'])]
|
||||||
|
public string $version = '';
|
||||||
|
}
|
||||||
26
src/State/AppVersionProvider.php
Normal file
26
src/State/AppVersionProvider.php
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\State;
|
||||||
|
|
||||||
|
use ApiPlatform\Metadata\Operation;
|
||||||
|
use ApiPlatform\State\ProviderInterface;
|
||||||
|
use App\ApiResource\AppVersion;
|
||||||
|
use Symfony\Component\DependencyInjection\Attribute\Autowire;
|
||||||
|
|
||||||
|
final readonly class AppVersionProvider implements ProviderInterface
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
#[Autowire('%app.version%')]
|
||||||
|
private string $version,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public function provide(Operation $operation, array $uriVariables = [], array $context = []): AppVersion
|
||||||
|
{
|
||||||
|
$dto = new AppVersion();
|
||||||
|
$dto->version = $this->version;
|
||||||
|
|
||||||
|
return $dto;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user