# ============================================================ # .gitlab-ci.yml — npm package with trusted publishing (OIDC) # # Project path: all-tomei-projects/tomei-package/General/ # # Tag patterns: # test-vX.Y.Z-rc.N → npm publish --tag test (RC for test env) # staging-vX.Y.Z-rc.N → npm publish --tag staging (RC for staging env) # prod-vX.Y.Z → npm publish --tag latest (production, manual gate) # prod-vX.Y.Z-hotfix.N → npm publish --tag latest (hotfix, manual gate) # # No NPM_TOKEN secret needed — OIDC trusted publishing only. # # Required CI/CD variables (Settings → CI/CD → Variables): # DISCORD_WEBHOOK — Discord incoming webhook URL (masked) # # npm side setup required (npmjs.com): # 1. package.json → publishConfig.provenance: true # 2. npmjs.com → package Settings → Publishing Access # → Enable OIDC publishing for GitLab project: # all-tomei-projects/tomei-package/General/ # ============================================================ default: image: node:20-alpine stages: - validate - build - publish - notify # ──────────────────────────────────────────────────────────── # STAGE: validate # Runs lint and unit tests before publish # Skipped on prod tags — code already validated on test + staging # ──────────────────────────────────────────────────────────── lint: stage: validate cache: key: node-$CI_COMMIT_REF_SLUG paths: [node_modules/] script: - npm ci - npm run lint rules: - if: '$CI_COMMIT_TAG =~ /^test-v/' - if: '$CI_COMMIT_TAG =~ /^staging-v/' unit-test: stage: validate cache: key: node-$CI_COMMIT_REF_SLUG paths: [node_modules/] script: - npm ci - npm test coverage: '/Lines\s*:\s*(\d+\.?\d*)%/' artifacts: reports: coverage_report: coverage_format: cobertura path: coverage/cobertura-coverage.xml expire_in: 7 days rules: - if: '$CI_COMMIT_TAG =~ /^test-v/' - if: '$CI_COMMIT_TAG =~ /^staging-v/' # ──────────────────────────────────────────────────────────── # STAGE: build # Compiles the package (tsc / rollup / whatever your build uses) # Artifacts passed to publish jobs # Runs on all tag patterns # ──────────────────────────────────────────────────────────── build: stage: build cache: key: node-$CI_COMMIT_REF_SLUG paths: [node_modules/] script: - npm ci - npm run build artifacts: paths: - dist/ expire_in: 1 hour rules: - if: '$CI_COMMIT_TAG =~ /^test-v/' - if: '$CI_COMMIT_TAG =~ /^staging-v/' - if: '$CI_COMMIT_TAG =~ /^prod-v/' # ──────────────────────────────────────────────────────────── # STAGE: publish — test RC # Triggered by: test-vX.Y.Z-rc.N tag # Publishes with dist-tag "test" # Consumers must opt in: npm install @tomei/pkg@test # Version stamped: test-v1.24.0-rc.1 → 1.24.0-rc.1 # ──────────────────────────────────────────────────────────── publish-test: stage: publish needs: [build] rules: - if: '$CI_COMMIT_TAG =~ /^test-v/' script: - echo "//registry.npmjs.org/:_authToken=${NPM_TOKEN}" > ~/.npmrc - | RC_VERSION=$(echo "$CI_COMMIT_TAG" | sed 's/^test-v\([0-9.]*\)-rc\.\([0-9]*\)$/\1-test.\2/') echo "Publishing version $RC_VERSION with dist-tag: test" npm version "$RC_VERSION" --no-git-tag-version --ignore-scripts --force npm publish --tag test --access public echo "Published: @$(node -p "require('./package.json').name")@$RC_VERSION [test]" # ──────────────────────────────────────────────────────────── # STAGE: publish — production # Triggered by: prod-vX.Y.Z or prod-vX.Y.Z-hotfix.N tag (from main only) # Publishes with dist-tag "latest" — becomes the default install version # Manual gate — human must click Play in GitLab pipeline UI # Version stamped: prod-v2.2.0 → 2.2.0 | prod-v2.2.0-hotfix.1 → 2.2.0-hotfix.1 # ──────────────────────────────────────────────────────────── publish-prod: stage: publish needs: [build] rules: - if: '$CI_COMMIT_TAG =~ /^prod-v/' when: manual allow_failure: false script: - echo "//registry.npmjs.org/:_authToken=${NPM_TOKEN}" > ~/.npmrc - | PROD_VERSION=$(echo "$CI_COMMIT_TAG" | sed 's/^prod-v//') echo "Publishing version $PROD_VERSION with dist-tag: latest" npm publish --tag latest --access public echo "Published: @$(node -p "require('./package.json').name")@$PROD_VERSION [latest]" echo "Pipeline: $CI_PIPELINE_URL" # ──────────────────────────────────────────────────────────── # STAGE: notify — success # ──────────────────────────────────────────────────────────── notify-success: stage: notify image: curlimages/curl:latest needs: - job: publish-test optional: true - job: publish-staging optional: true - job: publish-prod optional: true rules: - if: '$CI_COMMIT_TAG =~ /^test-v/' when: on_success - if: '$CI_COMMIT_TAG =~ /^staging-v/' when: on_success - if: '$CI_COMMIT_TAG =~ /^prod-v/' when: on_success script: - | if echo "$CI_COMMIT_TAG" | grep -q "^test-v"; then DIST_TAG="test" COLOR=3066993 elif echo "$CI_COMMIT_TAG" | grep -q "^staging-v"; then DIST_TAG="staging" COLOR=16776960 elif echo "$CI_COMMIT_TAG" | grep -q "^prod-v"; then DIST_TAG="latest" COLOR=3066993 fi curl -s -X POST "$DISCORD_WEBHOOK" \ -H "Content-Type: application/json" \ -d '{ "embeds": [{ "title": "Package published successfully", "color": '"$COLOR"', "fields": [ {"name": "Package", "value": "'"$CI_PROJECT_NAME"'", "inline": true}, {"name": "Tag", "value": "'"$CI_COMMIT_TAG"'", "inline": true}, {"name": "Dist-tag", "value": "'"$DIST_TAG"'", "inline": true}, {"name": "Published by", "value": "'"$GITLAB_USER_NAME"'", "inline": true}, {"name": "Pipeline", "value": "'"$CI_PIPELINE_URL"'", "inline": false} ] }] }' # ──────────────────────────────────────────────────────────── # STAGE: notify — failure # ──────────────────────────────────────────────────────────── notify-failure: stage: notify image: curlimages/curl:latest rules: - if: '$CI_COMMIT_TAG =~ /^test-v/' when: on_failure - if: '$CI_COMMIT_TAG =~ /^staging-v/' when: on_failure - if: '$CI_COMMIT_TAG =~ /^prod-v/' when: on_failure script: - | curl -s -X POST "$DISCORD_WEBHOOK" \ -H "Content-Type: application/json" \ -d '{ "embeds": [{ "title": "Package publish FAILED", "color": 15158332, "fields": [ {"name": "Package", "value": "'"$CI_PROJECT_NAME"'", "inline": true}, {"name": "Tag", "value": "'"$CI_COMMIT_TAG"'", "inline": true}, {"name": "Failed by", "value": "'"$GITLAB_USER_NAME"'", "inline": true}, {"name": "Pipeline", "value": "'"$CI_PIPELINE_URL"'", "inline": false} ] }] }'