docs(readme): mis à jour de la documentation #15
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Semantic Versioning | |
| on: | |
| push: | |
| branches: | |
| - main | |
| pull_request: | |
| branches: | |
| - main | |
| workflow_dispatch: | |
| inputs: | |
| dry_run: | |
| description: "Dry-run mode (no commits/tags)" | |
| required: false | |
| type: boolean | |
| default: false | |
| force_version: | |
| description: "Force specific version (e.g., 1.0.0)" | |
| type: string | |
| required: false | |
| workflow_call: | |
| inputs: | |
| dry_run: | |
| description: "Dry-run mode (no commits/tags)" | |
| required: false | |
| type: boolean | |
| default: false | |
| force_version: | |
| description: "Force specific version (e.g., 1.0.0)" | |
| type: string | |
| required: false | |
| jobs: | |
| versioning: | |
| name: Semantic Release | |
| runs-on: ubuntu-latest | |
| outputs: | |
| handle: ${{ steps.tag.outputs.new_version }} | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| fetch-tags: true | |
| persist-credentials: true | |
| - name: Setup commit info | |
| id: commit_info | |
| run: | | |
| if [ "${{ github.event_name }}" == "pull_request" ]; then | |
| USER_NAME="${{ github.event.pull_request.user.login }}" | |
| USER_EMAIL="${USER_NAME}@users.noreply.github.com" | |
| COMMIT_MESSAGE="${{ github.event.pull_request.title }}" | |
| else | |
| USER_NAME="${{ github.event.head_commit.author.name }}" | |
| USER_EMAIL="${{ github.event.head_commit.author.email }}" | |
| COMMIT_MESSAGE="${{ github.event.head_commit.message }}" | |
| fi | |
| if [ "${{ github.event_name }}" == "workflow_dispatch" ]; then | |
| echo "dry_run=${{ github.event.inputs.dry_run }}" >> $GITHUB_ENV | |
| else | |
| echo "dry_run=false" >> $GITHUB_ENV | |
| fi | |
| echo "max_attempts=3" >> $GITHUB_ENV | |
| echo "retry_delay=5" >> $GITHUB_ENV | |
| echo "commit_count=0" >> $GITHUB_ENV | |
| echo "feature_count=0" >> $GITHUB_ENV | |
| echo "fix_count=0" >> $GITHUB_ENV | |
| echo "refactor_count=0" >> $GITHUB_ENV | |
| echo "misc_count=0" >> $GITHUB_ENV | |
| echo "merge_count=0" >> $GITHUB_ENV | |
| echo "bump_type=initial" >> $GITHUB_ENV | |
| echo "user_name=$USER_NAME" >> $GITHUB_ENV | |
| echo "user_email=$USER_EMAIL" >> $GITHUB_ENV | |
| echo "commit_message=$COMMIT_MESSAGE" >> $GITHUB_ENV | |
| echo "event_name=${{ github.event_name }}" >> $GITHUB_ENV | |
| echo "workflow_run_id=${{ github.run_id }}" >> $GITHUB_ENV | |
| echo "workflow_run_number=${{ github.run_number }}" >> $GITHUB_ENV | |
| echo "workflow_timestamp=$(date -u +'%Y-%m-%dT%H:%M:%SZ')" >> $GITHUB_ENV | |
| echo "[AUDIT] ===== WORKFLOW START =====" | |
| echo "[AUDIT] Event: ${{ github.event_name }}" | |
| echo "[AUDIT] User: $USER_NAME" | |
| echo "[AUDIT] Branch: ${{ github.ref_name }}" | |
| echo "[AUDIT] Repository: ${{ github.repository }}" | |
| echo "[AUDIT] Commit: ${{ github.sha }}" | |
| echo "[AUDIT] Run ID: ${{ github.run_id }}" | |
| if [ "$DRY_RUN" == "true" ]; then | |
| echo "[AUDIT] 🔍 DRY-RUN MODE ENABLED - No commits or tags will be created" | |
| fi | |
| - name: Get latest tag | |
| id: get_tag | |
| run: | | |
| tag=$(git describe --tags --abbrev=0 2>/dev/null || echo "not_init") | |
| echo "last_tag=$tag" >> $GITHUB_ENV | |
| echo "[AUDIT] Last tag found: $tag" | |
| - name: Get commits since last tag | |
| id: commits | |
| run: | | |
| # Vérifie si le tag existe | |
| if git rev-parse ${{ env.last_tag }} >/dev/null 2>&1; then | |
| echo "[AUDIT] Tag exists, getting commits since ${{ env.last_tag }}" | |
| commits=$(git log ${{ env.last_tag }}..HEAD --pretty=format:"%s%n%b") | |
| else | |
| echo "[AUDIT] No previous tag found, using all commits" | |
| commits=$(git log HEAD --pretty=format:"%s%n%b") | |
| new_version="v0.0.0" | |
| echo "new_version=$new_version" >> $GITHUB_ENV | |
| fi | |
| commit_count=$(echo "$commits" | wc -l) | |
| echo "commit_count=$commit_count" >> $GITHUB_ENV | |
| echo "[AUDIT] Total commits: $commit_count" | |
| { | |
| echo "commits<<EOF" | |
| echo "$commits" | |
| echo "EOF" | |
| } >> $GITHUB_ENV | |
| - name: Determine next version | |
| if: ${{ env.last_tag != 'not_init' }} | |
| id: bump | |
| run: | | |
| LAST_TAG="${{ env.last_tag }}" | |
| major=$(echo "$LAST_TAG" | cut -d. -f1 | tr -d v) | |
| minor=$(echo "$LAST_TAG" | cut -d. -f2) | |
| patch=$(echo "$LAST_TAG" | cut -d. -f3) | |
| bump="none" | |
| if echo "${{ env.commits }}" | grep -E -q "(BREAKING CHANGE|!:)"; then | |
| bump="major" | |
| elif echo "${{ env.commits }}" | grep -E -q "^(feat|✨|🚀)"; then | |
| bump="minor" | |
| elif echo "${{ env.commits }}" | grep -E -q "^(fix|🐛|perf|⚡|refactor|♻️|🎨)"; then | |
| bump="patch" | |
| fi | |
| if [ "$bump" = "none" ]; then | |
| echo "[AUDIT] No version bump needed" | |
| new_version="$LAST_TAG" | |
| echo "new_version=$new_version" >> $GITHUB_ENV | |
| exit 0 | |
| fi | |
| if [ "$bump" = "major" ]; then | |
| major=$((major+1)); minor=0; patch=0 | |
| elif [ "$bump" = "minor" ]; then | |
| minor=$((minor+1)); patch=0 | |
| elif [ "$bump" = "patch" ]; then | |
| patch=$((patch+1)) | |
| fi | |
| new_version="v${major}.${minor}.${patch}" | |
| echo "bump_type=$bump" >> $GITHUB_ENV | |
| echo "new_version=$new_version" >> $GITHUB_ENV | |
| echo "[AUDIT] Version bump: $bump → $new_version" | |
| - name: Generate changelog text | |
| if: ${{ env.event_name == 'push' &&!startsWith(env.commit_message, 'docs(changelog)') }} | |
| id: changelog | |
| run: | | |
| echo "[AUDIT] Starting changelog generation" | |
| VERSION_TITLE="## ${{ env.new_version }}" | |
| TMP_FILE="CHANGELOG_TMP.md" | |
| FINAL_FILE="CHANGELOG.md" | |
| REPO_URL="https://github.com/${{ github.repository }}" | |
| LAST_TAG="${{ env.last_tag }}" | |
| extract_commits() { | |
| local pattern="$1" | |
| echo "$ALL_COMMITS_FORMATTED" | grep -E "^- ${pattern}" | while read -r line; do | |
| # Si le commit commence par le pattern (type connu) | |
| if [[ "$line" =~ ^-[[:space:]]$pattern(\(|\\|\!|:) ]]; then | |
| # echo "New line matched pattern '$pattern': $line" >&2 | |
| echo "$line" | sed -E 's#(\\!:|!:)#:#g; s#^- (feat|fix|refactor|perf)(\(([^)]+)\)):#- \3:#; s#^- :#- #g' | |
| else | |
| # echo "Line not matching pattern '$pattern': $line" >&2 | |
| echo "$line" | |
| fi | |
| done | |
| } | |
| extract_misc_commits() { | |
| # Tout ce qui n'est pas feat, fix, refactor, perf, BREAKING CHANGE | |
| echo "$ALL_COMMITS_FORMATTED" \ | |
| | grep -Ev "^- ((feat|fix|refactor|perf)(\(|:)|(✨|🚀|🐛|♻️|⚡|🎨))|BREAKING CHANGE|!:|Merge" | sed -E "s|//||g; s|\\?!:|:|g" || true | |
| } | |
| extract_merge_commits() { | |
| # Tout ce qui n'est pas feat, fix, refactor, perf, BREAKING CHANGE | |
| echo "$ALL_COMMITS_FORMATTED" \ | |
| | grep "Merge" || true | |
| } | |
| if [[ "${{ env.last_tag }}" != "not_init" ]]; then | |
| ALL_COMMITS_FORMATTED="$(git log "${LAST_TAG}..HEAD" --pretty=format:"- %s ([commit %h](${REPO_URL}/commit/%H)) by %an" || true)" | |
| echo "$ALL_COMMITS_FORMATTED" | |
| echo "$VERSION_TITLE - $(date +'%Y-%m-%d')" > $TMP_FILE | |
| echo "" >> $TMP_FILE | |
| # --- ⚠️ BREAKING CHANGE --- | |
| breakingChange=$(echo "$ALL_COMMITS_FORMATTED" | grep -E "(BREAKING CHANGE|!:)" | sed -E "s|//||g; s|\\?!:|:|g" || true) | |
| if [ -n "$breakingChange" ]; then | |
| echo "### ⚠️ Breaking Changes" >> "$TMP_FILE" | |
| echo "${breakingChange//!:/:}" >> "$TMP_FILE" | |
| echo "" >> "$TMP_FILE" | |
| echo "[AUDIT] ⚠️ Found breaking changes" | |
| fi | |
| # --- 🚀 Features --- | |
| features=$(extract_commits "(feat|✨|🚀)") | |
| if [ -n "$features" ]; then | |
| echo "### 🚀 Features" >> "$TMP_FILE" | |
| echo "$features" >> "$TMP_FILE" | |
| echo "" >> "$TMP_FILE" | |
| feature_count=$(echo "$features" | wc -l) | |
| echo "feature_count=$feature_count" >> $GITHUB_ENV | |
| echo "" | |
| echo "[AUDIT] 🚀 Found $feature_count features" | |
| fi | |
| # --- 🐛 Fixes --- | |
| fixes=$(extract_commits "(fix|🐛)") | |
| if [ -n "$fixes" ]; then | |
| echo "### 🐛 Fixes" >> "$TMP_FILE" | |
| echo "$fixes" >> "$TMP_FILE" | |
| echo "" >> "$TMP_FILE" | |
| fix_count=$(echo "$fixes" | wc -l) | |
| echo "fix_count=$fix_count" >> $GITHUB_ENV | |
| echo "" | |
| echo "[AUDIT] 🐛 Found $fix_count fixes" | |
| fi | |
| # --- ♻️ Refactors / Performance --- | |
| refactors=$(extract_commits "(refactor|♻️|perf|⚡|🎨)") | |
| if [ -n "$refactors" ]; then | |
| echo "### ♻️ Refactors / Performance" >> "$TMP_FILE" | |
| echo "$refactors" >> "$TMP_FILE" | |
| echo "" >> "$TMP_FILE" | |
| refactor_count=$(echo "$refactors" | wc -l) | |
| echo "refactor_count=$refactor_count" >> $GITHUB_ENV | |
| echo "" | |
| echo "[AUDIT] ♻️ Found $refactor_count refactors" | |
| fi | |
| # --- 📝 Autres changements --- | |
| misc=$(extract_misc_commits) | |
| if [ -n "$misc" ]; then | |
| echo "### 📝 Autres changements" >> "$TMP_FILE" | |
| echo "$misc" >> "$TMP_FILE" | |
| echo "" >> "$TMP_FILE" | |
| misc_count=$(echo "$misc" | wc -l) | |
| echo "misc_count=$misc_count" >> $GITHUB_ENV | |
| echo "" | |
| echo "[AUDIT] 📝 Found $misc_count misc commits" | |
| fi | |
| # --- 🔀 Merges & Pull Requests --- | |
| merge=$(extract_merge_commits) | |
| if [ -n "$merge" ]; then | |
| echo "### 🔀 Merges" >> "$TMP_FILE" | |
| echo "$merge" >> "$TMP_FILE" | |
| echo "" >> "$TMP_FILE" | |
| merge_count=$(echo "$merge" | wc -l) | |
| echo "merge_count=$merge_count" >> $GITHUB_ENV | |
| echo "" | |
| echo "[AUDIT] Found 🔀 $merge_count merge commits" | |
| fi | |
| contributors=$(git log "${LAST_TAG}..HEAD" --pretty=format:"%an" | sort -u | wc -l) | |
| files_changed=$(git log "${LAST_TAG}..HEAD" --name-only --pretty=format: | sort -u | wc -l) | |
| start_date=$(git log "${LAST_TAG}" -1 --format=%cs) | |
| end_date=$(date +'%Y-%m-%d') | |
| impact_score=$(( feature_count*3 + fix_count*2 + refactor_count + misc_count/2 )) | |
| echo "period=${start_date} → ${end_date}" >> $GITHUB_ENV | |
| echo "contributors=$contributors" >> $GITHUB_ENV | |
| echo "files_changed=$files_changed" >> $GITHUB_ENV | |
| echo "impact_score=$impact_score" >> $GITHUB_ENV | |
| echo "### 📊 Release Stats" >> "$TMP_FILE" | |
| echo "- Commits: ${{ env.commit_count }}" >> "$TMP_FILE" | |
| echo "- Contributors: $contributors" >> "$TMP_FILE" | |
| echo "- Files changed: $files_changed" >> "$TMP_FILE" | |
| echo "- Period: $start_date → $end_date" >> "$TMP_FILE" | |
| echo "- Impact score: $impact_score 🚀" >> "$TMP_FILE" | |
| if [ "${{ env.last_tag }}" != "${{ env.new_version }}" ]; then | |
| echo "- Compare changes: [${{ env.last_tag }}...${{ env.new_version }}](${REPO_URL}/compare/${{ env.last_tag }}...${{ env.new_version }})" >> $TMP_FILE | |
| fi | |
| echo "" >> "$TMP_FILE" | |
| contributors_list=$(git log "${LAST_TAG}..HEAD" --pretty=format:"%an" | sort -u) | |
| if [ -n "$contributors_list" ]; then | |
| echo "### 🤝 Contributors" >> "$TMP_FILE" | |
| echo "$contributors_list" | sed 's/^/- /' >> "$TMP_FILE" | |
| echo "" >> "$TMP_FILE" | |
| fi | |
| # --- Fusion si le changelog existe déjà --- | |
| if [ -f "$FINAL_FILE" ] && grep -q "^$VERSION_TITLE" "$FINAL_FILE"; then | |
| echo "[AUDIT] Merging new commits under existing version" | |
| # Extraire tout le changelog sauf la première occurrence du titre | |
| awk -v ver="$VERSION_TITLE" ' | |
| BEGIN {found=0} | |
| { | |
| if ($0 ~ ver && found==0) {found=1; next} | |
| }' "$FINAL_FILE" > CHANGELOG_OLD.tmp | |
| # Concaténer le nouveau bloc + ancien contenu (sans doublon) | |
| cat "$TMP_FILE" CHANGELOG_OLD.tmp > "$FINAL_FILE" | |
| else | |
| # Nouveau changelog ou nouvelle version | |
| if [ -f "$FINAL_FILE" ]; then | |
| cat "$TMP_FILE" "$FINAL_FILE" > CHANGELOG_NEW.tmp | |
| mv CHANGELOG_NEW.tmp "$FINAL_FILE" | |
| else | |
| mv "$TMP_FILE" "$FINAL_FILE" | |
| fi | |
| fi | |
| echo "[AUDIT] Changelog generation completed" | |
| echo "CHANGELOG<<EOF" >> $GITHUB_ENV | |
| cat "$FINAL_FILE" >> $GITHUB_ENV | |
| echo "EOF" >> $GITHUB_ENV | |
| else | |
| echo "[AUDIT] Skipping changelog generation (first version)" | |
| touch "$FINAL_FILE" | |
| fi | |
| - name: Commit changelog | |
| if: ${{env.event_name == 'push' && !startsWith(env.commit_message, 'docs(changelog)') && env.dry_run == 'false' }} | |
| run: | | |
| MAX_ATTEMPTS="${{ env.max_attempts }}" | |
| RETRY_DELAY="${{ env.retry_delay }}" | |
| echo "[AUDIT] Starting changelog commit" | |
| git config user.name "${{ env.user_name }}" | |
| git config user.email "${{ env.user_email }}" | |
| echo "[AUDIT] Git configured as ${{ env.user_name }} <${{ env.user_email }}>" | |
| git add CHANGELOG.md | |
| if ! git diff --cached --quiet; then | |
| git commit -m "docs(changelog): update for ${{ env.new_version }}" | |
| ATTEMPT=1 | |
| while [ $ATTEMPT -le $MAX_ATTEMPTS ]; do | |
| echo "[AUDIT] Push attempt ${ATTEMPT}/${MAX_ATTEMPTS}..." | |
| if git push; then | |
| echo "[AUDIT] Changelog committed and pushed successfully" | |
| exit 0 | |
| fi | |
| echo "[AUDIT] Push failed, retrying in ${RETRY_DELAY}s... (attempt ${ATTEMPT}/${MAX_ATTEMPTS})" | |
| sleep $RETRY_DELAY | |
| ATTEMPT=$((ATTEMPT+1)) | |
| done | |
| if [ $ATTEMPT -gt $MAX_ATTEMPTS ]; then | |
| echo "[ERROR] Failed to push changelog after ${MAX_ATTEMPTS} attempts" | |
| exit 1 | |
| fi | |
| else | |
| echo "[AUDIT] No changelog updates to push" | |
| fi | |
| - name: Tag new version | |
| if: ${{ env.event_name == 'push' && env.dry_run == 'false' }} | |
| id: tag | |
| run: | | |
| MAX_ATTEMPTS="${{ env.max_attempts }}" | |
| RETRY_DELAY="${{ env.retry_delay }}" | |
| retry_push() { | |
| local push_cmd="$1" | |
| local action_desc="$2" | |
| local attempt=1 | |
| while [ $attempt -le $MAX_ATTEMPTS ]; do | |
| echo "[AUDIT] $action_desc - Attempt $attempt/$MAX_ATTEMPTS..." | |
| if eval "$push_cmd"; then | |
| echo "[AUDIT] ✅ $action_desc succeeded" | |
| return 0 | |
| fi | |
| if [ $attempt -lt $MAX_ATTEMPTS ]; then | |
| echo "[AUDIT] ⏳ $action_desc failed, retrying in ${RETRY_DELAY}s..." | |
| sleep $RETRY_DELAY | |
| fi | |
| attempt=$((attempt+1)) | |
| done | |
| echo "[ERROR] ❌ $action_desc failed after $MAX_ATTEMPTS attempts" | |
| return 1 | |
| } | |
| echo "[AUDIT] Starting tag creation" | |
| TAG="${{ env.new_version }}" | |
| CURRENT_COMMIT=$(git rev-parse HEAD) | |
| # Vérifie si le tag existe déjà | |
| if git rev-parse "$TAG" >/dev/null 2>&1; then | |
| TAG_COMMIT=$(git rev-list -n 1 "$TAG") | |
| if [ "$TAG_COMMIT" = "$CURRENT_COMMIT" ]; then | |
| echo "[AUDIT] ✅ Tag $TAG already points to HEAD. Skipping re-tag." | |
| echo "new_version=$TAG" >> $GITHUB_OUTPUT | |
| exit 0 | |
| else | |
| echo "[AUDIT] ⚠️ Tag $TAG exists but points to another commit. Updating it safely..." | |
| git tag -d "$TAG" | |
| git tag "$TAG" | |
| git push origin ":refs/tags/$TAG" | |
| git push origin "$TAG" | |
| if ! retry_push "git push origin ':refs/tags/$TAG' 2>/dev/null" "Deleting old tag"; then | |
| echo "[WARN] Could not delete old tag on remote, but continuing with new tag..." | |
| fi | |
| fi | |
| else | |
| echo "[AUDIT] 🏷️ Creating new tag $TAG" | |
| git tag "$TAG" | |
| fi | |
| # Retry pour pusher le nouveau tag | |
| if retry_push "git push origin '$TAG'" "Pushing new tag"; then | |
| echo "[AUDIT] Tag created and pushed successfully" | |
| echo "new_version=$TAG" >> $GITHUB_OUTPUT | |
| exit 0 | |
| else | |
| echo "[ERROR] Failed to push tag after $MAX_ATTEMPTS attempts" | |
| exit 1 | |
| fi | |
| - name: Preview changes (dry-run) | |
| if: ${{ env.dry_run == 'true' }} | |
| run: | | |
| echo "" | |
| echo "========== DRY-RUN PREVIEW ==========" | |
| echo "Version: ${{ env.new_version }}" | |
| echo "Bump Type: ${{ env.bump_type }}" | |
| echo "" | |
| echo "Would create:" | |
| echo " - Changelog entry" | |
| echo " - Commit: docs(changelog): update for ${{ env.new_version }}" | |
| echo " - Tag: ${{ env.new_version }}" | |
| echo "" | |
| echo "Stats:" | |
| echo " - Features: ${{ env.feature_count }}" | |
| echo " - Fixes: ${{ env.fix_count }}" | |
| echo " - Refactors: ${{ env.refactor_count }}" | |
| echo " - Other changes: ${{ env.misc_count }}" | |
| echo " - Merges: ${{ env.merge_count }}" | |
| echo "" | |
| echo "Preview of changelog:" | |
| echo "---" | |
| if [ -f CHANGELOG.md ]; then | |
| head -n 30 CHANGELOG.md | |
| else | |
| echo "(No changelog generated)" | |
| fi | |
| echo "---" | |
| echo "====================================" | |
| echo "" | |
| - name: Output version | |
| run: | | |
| echo "" | |
| echo "========== WORKFLOW AUDIT SUMMARY ==========" | |
| echo "Timestamp: ${{ env.workflow_timestamp }}" | |
| echo "Run ID: ${{ env.workflow_run_id }}" | |
| echo "Run Number: ${{ env.workflow_run_number }}" | |
| echo "Event: ${{ env.event_name }}" | |
| echo "Branch: ${{ github.ref_name }}" | |
| echo "User: ${{ env.user_name }}" | |
| echo "Repository: ${{ github.repository }}" | |
| echo "Commit: ${{ github.sha }}" | |
| echo "" | |
| echo "Version Details:" | |
| echo " Previous: ${{ env.last_tag }}" | |
| echo " New: ${{ env.new_version }}" | |
| echo " Bump Type: ${{ env.bump_type }}" | |
| echo " Dry-run: ${{ env.dry_run }}" | |
| echo "" | |
| echo "Changelog Stats:" | |
| echo " Commits: ${{ env.commit_count }}" | |
| echo " Features: ${{ env.feature_count }}" | |
| echo " Fixes: ${{ env.fix_count }}" | |
| echo " Refactors: ${{ env.refactor_count }}" | |
| echo " Other: ${{ env.misc_count }}" | |
| echo " Merges: ${{ env.merge_count }}" | |
| echo "" | |
| echo "Global Stats:" | |
| echo " Impact Score: ${{ env.impact_score }} 🚀" | |
| echo " Contributors: ${{ env.contributors }}" | |
| echo " Files Changed: ${{ env.files_changed }}" | |
| echo " Period: ${{ env.period }}" | |
| echo "" | |
| echo "==========================================" | |
| echo "" |