From e73b68e8d27c739177f994b2d00d2e88579a3eb4 Mon Sep 17 00:00:00 2001 From: TO Date: Tue, 5 May 2026 21:07:25 +0200 Subject: [PATCH] chore: migrate CI/CD pipeline from GitHub Actions to GitLab CI --- .github/copilot-instructions.md | 47 --------- .github/dependabot.yml | 34 ------- .github/workflows/codeql.yml | 41 -------- .github/workflows/dependabot-auto-merge.yml | 28 ------ .github/workflows/docker-build-push.yml | 69 ------------- .github/workflows/pr-validation.yml | 105 -------------------- .gitlab-ci.yml | 97 ++++++++++++++++++ package-lock.json | 17 ++-- package.json | 1 + 9 files changed, 106 insertions(+), 333 deletions(-) delete mode 100644 .github/copilot-instructions.md delete mode 100644 .github/dependabot.yml delete mode 100644 .github/workflows/codeql.yml delete mode 100644 .github/workflows/dependabot-auto-merge.yml delete mode 100644 .github/workflows/docker-build-push.yml delete mode 100644 .github/workflows/pr-validation.yml create mode 100644 .gitlab-ci.yml diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md deleted file mode 100644 index 9cb8b7f..0000000 --- a/.github/copilot-instructions.md +++ /dev/null @@ -1,47 +0,0 @@ -You are an expert in TypeScript, Angular, and scalable web application development. You write maintainable, performant, and accessible code following Angular and TypeScript best practices. - -## TypeScript Best Practices - -- Use strict type checking -- Prefer type inference when the type is obvious -- Avoid the `any` type; use `unknown` when type is uncertain - -## Angular Best Practices - -- Always use standalone components over NgModules -- Must NOT set `standalone: true` inside Angular decorators. It's the default. -- Use signals for state management -- Implement lazy loading for feature routes -- Do NOT use the `@HostBinding` and `@HostListener` decorators. Put host bindings inside the `host` object of the `@Component` or `@Directive` decorator instead -- Use `NgOptimizedImage` for all static images. - - `NgOptimizedImage` does not work for inline base64 images. - -## Components - -- Keep components small and focused on a single responsibility -- Use `input()` and `output()` functions instead of decorators -- Use `computed()` for derived state -- Set `changeDetection: ChangeDetectionStrategy.OnPush` in `@Component` decorator -- Prefer inline templates for small components -- Prefer Reactive forms instead of Template-driven ones -- Do NOT use `ngClass`, use `class` bindings instead -- Do NOT use `ngStyle`, use `style` bindings instead - -## State Management - -- Use signals for local component state -- Use `computed()` for derived state -- Keep state transformations pure and predictable -- Do NOT use `mutate` on signals, use `update` or `set` instead - -## Templates - -- Keep templates simple and avoid complex logic -- Use native control flow (`@if`, `@for`, `@switch`) instead of `*ngIf`, `*ngFor`, `*ngSwitch` -- Use the async pipe to handle observables - -## Services - -- Design services around a single responsibility -- Use the `providedIn: 'root'` option for singleton services -- Use the `inject()` function instead of constructor injection diff --git a/.github/dependabot.yml b/.github/dependabot.yml deleted file mode 100644 index 4feaae7..0000000 --- a/.github/dependabot.yml +++ /dev/null @@ -1,34 +0,0 @@ -version: 2 -updates: - # Enable version updates for npm - - package-ecosystem: 'npm' - directory: '/' - schedule: - interval: 'weekly' - day: 'monday' - open-pull-requests-limit: 10 - groups: - angular: - patterns: - - '@angular/*' - development-dependencies: - dependency-type: 'development' - update-types: - - 'minor' - - 'patch' - - # Enable version updates for GitHub Actions - - package-ecosystem: 'github-actions' - directory: '/' - schedule: - interval: 'weekly' - day: 'monday' - open-pull-requests-limit: 5 - - # Enable version updates for Docker - - package-ecosystem: 'docker' - directory: '/' - schedule: - interval: 'weekly' - day: 'monday' - open-pull-requests-limit: 5 diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml deleted file mode 100644 index c21ed48..0000000 --- a/.github/workflows/codeql.yml +++ /dev/null @@ -1,41 +0,0 @@ -name: CodeQL Security Analysis - -on: - push: - branches: [main] - pull_request: - branches: [main] - schedule: - - cron: '0 0 * * 1' # Run every Monday at midnight UTC - -jobs: - analyze: - name: Analyze Code - runs-on: ubuntu-latest - permissions: - security-events: write - actions: read - contents: read - - strategy: - fail-fast: false - matrix: - language: ['javascript-typescript'] - - steps: - - name: Checkout repository - uses: actions/checkout@v6 - - - name: Initialize CodeQL - uses: github/codeql-action/init@v4 - with: - languages: ${{ matrix.language }} - queries: security-extended,security-and-quality - - - name: Autobuild - uses: github/codeql-action/autobuild@v4 - - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v4 - with: - category: '/language:${{matrix.language}}' diff --git a/.github/workflows/dependabot-auto-merge.yml b/.github/workflows/dependabot-auto-merge.yml deleted file mode 100644 index e2c6b86..0000000 --- a/.github/workflows/dependabot-auto-merge.yml +++ /dev/null @@ -1,28 +0,0 @@ -name: Dependabot Auto-merge - -on: - pull_request: - branches: [main] - -permissions: - contents: write - pull-requests: write - -jobs: - auto-merge: - name: Auto-merge Dependabot minor and patch updates - runs-on: ubuntu-latest - if: github.actor == 'dependabot[bot]' - steps: - - name: Fetch Dependabot metadata - id: metadata - uses: dependabot/fetch-metadata@v2.5.0 - - - name: Auto-merge minor and patch updates - if: | - steps.metadata.outputs.update-type == 'version-update:semver-minor' || - steps.metadata.outputs.update-type == 'version-update:semver-patch' - run: gh pr merge --auto --squash "$PR_URL" - env: - PR_URL: ${{ github.event.pull_request.html_url }} - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/docker-build-push.yml b/.github/workflows/docker-build-push.yml deleted file mode 100644 index 2894225..0000000 --- a/.github/workflows/docker-build-push.yml +++ /dev/null @@ -1,69 +0,0 @@ -name: Docker Build and Push - -on: - push: - branches: [main] - pull_request: - branches: [main] - types: [closed] - release: - types: [published] - workflow_dispatch: - -jobs: - build-and-push: - if: github.event_name != 'pull_request' || github.event.pull_request.merged == true - runs-on: ubuntu-latest - permissions: - contents: read - packages: write - security-events: write - - steps: - - uses: actions/checkout@v6 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Log in to GitHub Container Registry - uses: docker/login-action@v3 - with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Extract metadata - id: meta - uses: docker/metadata-action@v5 - with: - images: ghcr.io/${{ github.repository }} - tags: | - type=ref,event=branch - type=semver,pattern={{version}} - type=semver,pattern={{major}}.{{minor}} - type=sha - type=raw,value=latest,enable={{is_default_branch}} - - - name: Build and push - id: build - uses: docker/build-push-action@v6 - with: - context: . - push: true - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} - cache-from: type=gha - cache-to: type=gha,mode=max - - - name: Run Trivy vulnerability scanner - uses: aquasecurity/trivy-action@0.35.0 - with: - image-ref: ghcr.io/thoguet/vac-optimizer@${{ steps.build.outputs.digest }} - format: 'sarif' - output: 'trivy-results.sarif' - - - name: Upload Trivy results to GitHub Security tab - uses: github/codeql-action/upload-sarif@v4 - if: always() - with: - sarif_file: 'trivy-results.sarif' diff --git a/.github/workflows/pr-validation.yml b/.github/workflows/pr-validation.yml deleted file mode 100644 index ac91c2b..0000000 --- a/.github/workflows/pr-validation.yml +++ /dev/null @@ -1,105 +0,0 @@ -name: PR Validation - -on: - pull_request: - branches: [main] - push: - branches: [main] - -jobs: - lint: - name: Lint Check - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v6 - - - name: Setup Node.js - uses: actions/setup-node@v6 - with: - node-version: '20' - cache: 'npm' - - - name: Install dependencies - run: npm ci --legacy-peer-deps - - - name: Check code formatting with Prettier - run: npx prettier --check "src/**/*.{ts,html,scss,css,json}" - - - name: Run ESLint - run: npx eslint "src/**/*.ts" - - type-check: - name: TypeScript Type Check - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v6 - - - name: Setup Node.js - uses: actions/setup-node@v6 - with: - node-version: '20' - cache: 'npm' - - - name: Install dependencies - run: npm ci --legacy-peer-deps - - - name: Run TypeScript compiler - run: npx tsc --noEmit - - test: - name: Unit Tests - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v6 - - - name: Setup Node.js - uses: actions/setup-node@v6 - with: - node-version: '20' - cache: 'npm' - - - name: Install dependencies - run: npm ci --legacy-peer-deps - - - name: Run unit tests - run: npm test -- --watch=false --browsers=ChromeHeadless --code-coverage - - - name: Upload coverage reports - uses: codecov/codecov-action@v5 - if: always() - with: - token: ${{ secrets.CODECOV_TOKEN }} - files: ./coverage/vac-optimizer/lcov.info - fail_ci_if_error: false - - build: - name: Build Application - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v6 - - - name: Setup Node.js - uses: actions/setup-node@v6 - with: - node-version: '20' - cache: 'npm' - - - name: Install dependencies - run: npm ci --legacy-peer-deps - - - name: Build production bundle - run: npm run build -- --configuration production - env: - NG_BUILD_CACHE: '.angular/cache' - - - name: Check bundle size - run: | - du -sh dist/vac-optimizer/browser - echo "Build completed successfully" - - - name: Upload build artifacts - uses: actions/upload-artifact@v6 - with: - name: build-artifacts - path: dist/ - retention-days: 7 diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000..19279be --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,97 @@ +image: node:20 + +stages: + - validate + - test + - build + - security + - docker + +variables: + DOCKER_DRIVER: overlay2 + DOCKER_TLS_CERTDIR: "" + +cache: + key: + files: + - package-lock.json + paths: + - .npm/ + +before_script: + - npm ci --cache .npm --prefer-offline + +# -- Validate Stage -- + +lint: + stage: validate + script: + - npx eslint "src/**/*.ts" + +prettier: + stage: validate + script: + - npx prettier --check "src/**/*.{ts,html,scss,css,json}" + +type-check: + stage: validate + script: + - npx tsc --noEmit + +# -- Test Stage -- + +test: + stage: test + script: + - npm run test -- --watch=false --browsers=ChromeHeadless --code-coverage + artifacts: + when: always + paths: + - coverage/vac-optimizer/ + expire_in: 7 days + +# -- Build Stage -- + +build: + stage: build + script: + - npm run build -- --configuration production + artifacts: + paths: + - dist/ + expire_in: 7 days + +# -- Security Stage -- + +include: + - template: Security/SAST.gitlab-ci.yml + +trivy_scan: + stage: security + image: + name: aquasec/trivy:latest + entrypoint: [""] + before_script: [] # override global before_script + script: + # Trivy will scan the project directory + - trivy fs --format sarif --output trivy-results.sarif . + artifacts: + reports: + sast: trivy-results.sarif + expire_in: 7 days + +# -- Docker Stage -- + +docker-build: + stage: docker + image: docker:24.0.5 + services: + - docker:24.0.5-dind + before_script: + - docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" "$CI_REGISTRY" + script: + - docker build -t "$CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG" -t "$CI_REGISTRY_IMAGE:latest" . + - docker push "$CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG" + - docker push "$CI_REGISTRY_IMAGE:latest" + rules: + - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH diff --git a/package-lock.json b/package-lock.json index 618b4d4..5906803 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,6 +16,7 @@ "@angular/forms": "21.2.10", "@angular/material": "^21.2.8", "@angular/platform-browser": "21.2.10", + "@angular/platform-browser-dynamic": "21.2.10", "@angular/platform-server": "21.2.10", "@angular/router": "21.2.10", "@angular/ssr": "21.2.8", @@ -658,12 +659,10 @@ } }, "node_modules/@angular/platform-browser-dynamic": { - "version": "21.1.5", - "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-21.1.5.tgz", - "integrity": "sha512-Pd8nPbJSIONnze1WS9wLBAtaFw4TYIH+ZGjKHS9G1E9l09tDWtHWyB7dY82Sc//Nc8iR4V7dcsbUmFjOJHThww==", - "dev": true, + "version": "21.2.10", + "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-21.2.10.tgz", + "integrity": "sha512-+/HMJSLnF87EODkHj0AKE3Q8AfYO/8jpTfr731QmplqBtCoLlA/1XR8aYow2hB9YKL9HZWDb2qGkRtCXhrtt+w==", "license": "MIT", - "peer": true, "dependencies": { "tslib": "^2.3.0" }, @@ -671,10 +670,10 @@ "node": "^20.19.0 || ^22.12.0 || >=24.0.0" }, "peerDependencies": { - "@angular/common": "21.1.5", - "@angular/compiler": "21.1.5", - "@angular/core": "21.1.5", - "@angular/platform-browser": "21.1.5" + "@angular/common": "21.2.10", + "@angular/compiler": "21.2.10", + "@angular/core": "21.2.10", + "@angular/platform-browser": "21.2.10" } }, "node_modules/@angular/platform-server": { diff --git a/package.json b/package.json index dda476b..91fdc59 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,7 @@ "@angular/forms": "21.2.10", "@angular/material": "^21.2.8", "@angular/platform-browser": "21.2.10", + "@angular/platform-browser-dynamic": "21.2.10", "@angular/platform-server": "21.2.10", "@angular/router": "21.2.10", "@angular/ssr": "21.2.8",