← Kursa Dön
📄 Text · 30 min

GitHub Packages ve Registry

Giriş — Bir Fırının Hikayesi

Bir fırın düşünün. Bu fırın harika bir ekmek tarifi geliştirmiş. Şimdi iki seçenek var: ya her müşteri geldiğinde ekmeği sıfırdan yapacaklar ya da tarifi standartlaştırıp "her sabah 100 ekmek üret" diyecekler. Ama daha da iyisi: bu ekmeği sadece kendi dükkânlarında değil, diğer fırınlara ve marketlere de dağıtabilirler. İşte paket yayınlamak (publishing) tam olarak budur.

Yazılımda da aynı mantık geçerlidir. Bir utility fonksiyonu, bir UI component library veya bir API client yazdınız. Bunu her projede kopyala-yapıştır yapmak yerine, bir paket olarak yayınlar ve npm install ile kullanırsınız. GitHub Packages, bu paketleri GitHub ekosistemine entegre bir şekilde barındırmanızı sağlar.

GitHub Packages Nedir?

GitHub Packages, GitHub'ın entegre paket barındırma (registry) servisidir. Birden fazla paket ekosistemini destekler:

Desteklenen Registry'ler:
────────────────────────────────────
📦 npm          → JavaScript/TypeScript paketleri
🐳 Container    → Docker image'ları (ghcr.io)
🧊 NuGet        → .NET paketleri
☕ Maven         → Java paketleri
💎 RubyGems     → Ruby gem'leri

npm Registry vs GitHub Packages

npmjs.com (Varsayılan)          GitHub Packages
┌──────────────────────┐       ┌──────────────────────┐
│ • Herkese açık       │       │ • GitHub ile entegre  │
│ • npm install pkg    │       │ • Repo izinleri geçerli│
│ • Tüm JS ekosistemi │       │ • Actions ile kolay   │
│ • Anonim erişim      │       │ • Scoped packages     │
│ • Rate limit yüksek  │       │ • Private paketler    │
└──────────────────────┘       └──────────────────────┘

Ne zaman hangisi?
• Open source, herkesin kullanacağı → npmjs.com
• Şirket içi, private paketler → GitHub Packages
• Organization kapsamlı paylaşım → GitHub Packages
• Docker image'ları → GitHub Container Registry

npm Paketi Yayınlama

Adım 1: Paket Oluşturma

# Yeni paket projesi
mkdir acme-utils && cd acme-utils
npm init --scope=@acme-corp

# package.json
{
  "name": "@acme-corp/utils",
  "version": "1.0.0",
  "description": "Acme Corp shared utility functions",
  "main": "dist/index.js",
  "types": "dist/index.d.ts",
  "files": ["dist"],
  "scripts": {
    "build": "tsc",
    "prepublishOnly": "npm run build"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/acme-corp/utils.git"
  },
  "publishConfig": {
    "registry": "https://npm.pkg.github.com"
  }
}

⚠️ Dikkat: GitHub Packages npm registry'si scoped packages gerektirir. Paket adı @owner/package-name formatında olmalıdır ve scope (owner), GitHub kullanıcı adınız veya organization adınız olmalıdır.

Adım 2: Kod Yazma

// src/index.ts
export function formatCurrency(amount: number, currency: string = 'TRY'): string {
  return new Intl.NumberFormat('tr-TR', {
    style: 'currency',
    currency,
  }).format(amount);
}

export function slugify(text: string): string {
  return text
    .toLowerCase()
    .replace(/[^\w\s-]/g, '')
    .replace(/[\s_]+/g, '-')
    .replace(/^-+|-+$/g, '');
}

export function debounce<T extends (...args: any[]) => any>(
  fn: T,
  delay: number
): (...args: Parameters<T>) => void {
  let timer: ReturnType<typeof setTimeout>;
  return (...args: Parameters<T>) => {
    clearTimeout(timer);
    timer = setTimeout(() => fn(...args), delay);
  };
}

export function deepClone<T>(obj: T): T {
  return structuredClone(obj);
}
// tsconfig.json
{
  "compilerOptions": {
    "target": "ES2020",
    "module": "commonjs",
    "lib": ["ES2020"],
    "declaration": true,
    "outDir": "./dist",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist"]
}

Adım 3: Authentication

GitHub Packages'a publish etmek için kimlik doğrulama gerekir:

# Personal Access Token (PAT) oluştur
# GitHub → Settings → Developer settings → Personal access tokens → Tokens (classic)
# Gerekli izinler: read:packages, write:packages, delete:packages

# .npmrc dosyası (proje root'unda)
cat > .npmrc << 'EOF'
@acme-corp:registry=https://npm.pkg.github.com
//npm.pkg.github.com/:_authToken=${GITHUB_TOKEN}
EOF

# veya global login
npm login --scope=@acme-corp --registry=https://npm.pkg.github.com
# Username: github-username
# Password: ghp_xxxxxxxxxxxx (PAT token)
# Email: your@email.com

⚠️ Dikkat: .npmrc dosyasındaki token'ı asla repo'ya commit etmeyin! ${GITHUB_TOKEN} environment variable kullanın veya .npmrc'yi .gitignore'a ekleyin.

Adım 4: Publish

# Build et
npm run build

# Publish et
npm publish

# Çıktı:
# npm notice
# npm notice 📦  @acme-corp/utils@1.0.0
# npm notice Tarball Contents
# npm notice 1.2kB dist/index.js
# npm notice 0.8kB dist/index.d.ts
# npm notice 0.5kB package.json
# npm notice Tarball Details
# npm notice name:          @acme-corp/utils
# npm notice version:       1.0.0
# npm notice filename:      acme-corp-utils-1.0.0.tgz
# npm notice total files:   3
# + @acme-corp/utils@1.0.0

Adım 5: Paketi Kullanma

# Diğer projelerde .npmrc ayarı
echo "@acme-corp:registry=https://npm.pkg.github.com" >> .npmrc

# Install
npm install @acme-corp/utils
// Kullanım
import { formatCurrency, slugify, debounce } from '@acme-corp/utils';

console.log(formatCurrency(1500));        // ₺1.500,00
console.log(slugify('Merhaba Dünya!'));   // merhaba-dunya

GitHub Actions ile Otomatik Publish

Manuel publish yerine, tag push'landığında otomatik yayınlama:

# .github/workflows/publish.yml
name: Publish Package

on:
  release:
    types: [published]

jobs:
  publish:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write

    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: '20'
          registry-url: 'https://npm.pkg.github.com'

      - run: npm ci
      - run: npm test
      - run: npm run build
      - run: npm publish
        env:
          NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# Release oluşturunca otomatik publish olur
# GitHub UI → Releases → Draft a new release
# veya CLI ile:
git tag v1.1.0
git push origin v1.1.0
gh release create v1.1.0 --title "v1.1.0" --notes "Bug fixes"

Semantic Versioning ile Otomatik Versiyon

# .github/workflows/publish.yml (gelişmiş)
name: Release and Publish

on:
  push:
    branches: [main]

jobs:
  release:
    runs-on: ubuntu-latest
    permissions:
      contents: write
      packages: write

    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - uses: actions/setup-node@v4
        with:
          node-version: '20'
          registry-url: 'https://npm.pkg.github.com'

      # Conventional commits'e göre otomatik versiyon
      - name: Determine version bump
        id: version
        run: |
          # Son tag'den bu yana commit'leri analiz et
          COMMITS=$(git log $(git describe --tags --abbrev=0 2>/dev/null || echo HEAD~10)..HEAD --oneline)
          if echo "$COMMITS" | grep -q "BREAKING CHANGE\|feat!"; then
            echo "bump=major" >> $GITHUB_OUTPUT
          elif echo "$COMMITS" | grep -q "^feat"; then
            echo "bump=minor" >> $GITHUB_OUTPUT
          else
            echo "bump=patch" >> $GITHUB_OUTPUT
          fi

      - run: npm ci
      - run: npm test
      - run: npm run build

      - name: Bump version
        run: npm version ${{ steps.version.outputs.bump }} --no-git-tag-version

      - name: Publish
        run: npm publish
        env:
          NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

      - name: Push version bump
        run: |
          VERSION=$(node -p "require('./package.json').version")
          git config user.name "github-actions"
          git config user.email "actions@github.com"
          git add package.json
          git commit -m "chore: release v${VERSION}"
          git tag "v${VERSION}"
          git push origin main --tags

GitHub Container Registry (ghcr.io)

Docker Image Yayınlama

GitHub Container Registry, Docker image'larınızı GitHub'da barındırmanızı sağlar:

# Dockerfile
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

FROM node:20-alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY package*.json ./
EXPOSE 3000
CMD ["node", "dist/index.js"]
# Local'de build ve push
# 1. Login
echo $GITHUB_TOKEN | docker login ghcr.io -u USERNAME --password-stdin

# 2. Build
docker build -t ghcr.io/acme-corp/my-api:latest .
docker build -t ghcr.io/acme-corp/my-api:v1.0.0 .

# 3. Push
docker push ghcr.io/acme-corp/my-api:latest
docker push ghcr.io/acme-corp/my-api:v1.0.0

Actions ile Otomatik Docker Build ve Push

# .github/workflows/docker.yml
name: Build and Push Docker Image

on:
  push:
    branches: [main]
    tags: ['v*']

env:
  REGISTRY: ghcr.io
  IMAGE_NAME: ${{ github.repository }}

jobs:
  build-and-push:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write

    steps:
      - uses: actions/checkout@v4

      - name: Login to Container Registry
        uses: docker/login-action@v3
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Extract metadata
        id: meta
        uses: docker/metadata-action@v5
        with:
          images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
          tags: |
            type=ref,event=branch
            type=semver,pattern={{version}}
            type=semver,pattern={{major}}.{{minor}}
            type=sha

      - name: Build and push
        uses: docker/build-push-action@v5
        with:
          context: .
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}
          cache-from: type=gha
          cache-to: type=gha,mode=max
# Sonuç: Push edilen tag'ler
ghcr.io/acme-corp/my-api:main          # Branch adı
ghcr.io/acme-corp/my-api:1.0.0         # Semver
ghcr.io/acme-corp/my-api:1.0           # Major.minor
ghcr.io/acme-corp/my-api:sha-abc1234   # Commit SHA

# Kullanım
docker pull ghcr.io/acme-corp/my-api:1.0.0
docker run -p 3000:3000 ghcr.io/acme-corp/my-api:1.0.0

Container Image Görünürlüğü

GitHub Container Registry görünürlük:
• Private (varsayılan) → Sadece organization üyeleri
• Public → Herkes pull edebilir (login gerekmez)

Ayar: Packages → Package → Package settings → Change visibility

💡 İpucu: Public container image'lar için ghcr.io ücretsizdir ve rate limit çok yüksektir. Docker Hub'ın artan rate limit'lerinden kaçınmak için open source projelerinizi ghcr.io'ya taşımayı düşünün.

Paket Versiyonlama Best Practices

Semantic Versioning (SemVer)

MAJOR.MINOR.PATCH
  │      │      │
  │      │      └── Bug fix, geriye dönük uyumlu
  │      └── Yeni özellik, geriye dönük uyumlu
  └── Breaking change, geriye dönük UYUMSUZ

Örnekler:
1.0.0 → 1.0.1  (bug fix)
1.0.1 → 1.1.0  (yeni özellik)
1.1.0 → 2.0.0  (breaking change)

Pre-release:
1.0.0-alpha.1
1.0.0-beta.1
1.0.0-rc.1
1.0.0

Changelog Oluşturma

# CHANGELOG.md

## [1.2.0] - 2024-02-15

### Added
- `formatDate()` fonksiyonu eklendi
- TypeScript strict mode desteği

### Fixed
- `slugify()` Türkçe karakter sorunu düzeltildi

### Changed
- `debounce()` artık generic type destekliyor

## [1.1.0] - 2024-01-20

### Added
- `deepClone()` fonksiyonu eklendi

## [1.0.0] - 2024-01-01

### Added
- İlk sürüm: `formatCurrency()`, `slugify()`, `debounce()`

Paket Temizleme ve Yönetim

# Paket versiyonlarını listele
gh api user/packages/npm/utils/versions \
  --jq '.[].metadata.container.tags'

# Eski versiyonları sil
gh api -X DELETE user/packages/npm/utils/versions/VERSION_ID

# Container image versiyonlarını listele
gh api user/packages/container/my-api/versions \
  --jq '.[] | "\(.id) \(.metadata.container.tags)"'

Otomatik Eski Versiyon Temizleme

# .github/workflows/cleanup.yml
name: Cleanup old packages

on:
  schedule:
    - cron: '0 0 * * 0'  # Her Pazar gece yarısı

jobs:
  cleanup:
    runs-on: ubuntu-latest
    permissions:
      packages: write

    steps:
      - uses: actions/delete-package-versions@v5
        with:
          package-name: 'my-api'
          package-type: 'container'
          min-versions-to-keep: 10
          delete-only-untagged-versions: true

Monorepo'dan Çoklu Paket Yayınlama

Monorepo'da birden fazla paket olabilir. Hepsini tek workflow'dan yayınlamak:

my-monorepo/
├── packages/
│   ├── utils/       → @acme-corp/utils
│   ├── ui/          → @acme-corp/ui
│   └── config/      → @acme-corp/config
# .github/workflows/publish-packages.yml
name: Publish Changed Packages

on:
  push:
    branches: [main]
    paths:
      - 'packages/**'

jobs:
  publish:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write

    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - uses: actions/setup-node@v4
        with:
          node-version: '20'
          registry-url: 'https://npm.pkg.github.com'

      - uses: pnpm/action-setup@v2

      - run: pnpm install

      # Changeset ile hangi paketlerin değiştiğini tespit et
      - name: Publish changed packages
        run: pnpm -r publish --no-git-checks
        env:
          NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

Private vs Public Packages

GitHub Packages'da paketlerin görünürlüğü önemlidir:

Görünürlük Seçenekleri:
─────────────────────────────────────
Private:  Sadece organization üyeleri erişebilir
          (varsayılan)

Internal: Organization içinde herkes erişebilir
          (GitHub Enterprise)

Public:   Herkes erişebilir
          (npm install ile kurulabilir)

Ayar: Packages → Paket seç → Package Settings → Visibility

Private Package Erişimi

# Diğer geliştiriciler private package kullanmak için:

# 1. Personal Access Token oluştur (read:packages izni)
# 2. .npmrc ayarla
echo "@acme-corp:registry=https://npm.pkg.github.com" >> .npmrc
echo "//npm.pkg.github.com/:_authToken=TOKEN" >> .npmrc

# 3. Install
npm install @acme-corp/utils

Paket İzin Yönetimi

# Organization paket izinleri:
# Organization Settings → Packages → Default Permissions

# Repo seviyesinde paket bağlama:
# Repository Settings → Packages → Link a package

# Actions'ın paket yayınlama izni:
# Repository Settings → Actions → General →
# Workflow permissions → Read and write permissions

Yaygın Hatalar

1. Scope Eşleşmemesi

# ❌ Paket adı organization ile eşleşmiyor
# Organization: acme-corp
# Paket: @my-scope/utils → HATA!

# ✅ Paket adı organization ile eşleşmeli
# Paket: @acme-corp/utils → OK

2. Token İzin Eksikliği

# ❌ PAT'ta write:packages izni yok
npm ERR! 403 Forbidden

# ✅ PAT izinleri:
# read:packages — paket okuma
# write:packages — paket yayınlama  
# delete:packages — paket silme

3. .npmrc Konfigürasyon Hatası

# ❌ Registry URL yanlış
@acme-corp:registry=https://npm.github.com  # YANLIŞ!

# ✅ Doğru URL
@acme-corp:registry=https://npm.pkg.github.com

Özet

Bu derste GitHub Packages ile paket yayınlama ve yönetimi öğrendik:

  • 📦 GitHub Packages — GitHub entegre paket registry'si, birden fazla ekosistem

  • 🔧 npm publish — scoped package oluşturma, .npmrc konfigürasyonu

  • 🤖 Actions ile publish — tag/release tetikli otomatik yayınlama

  • 🐳 Container Registryghcr.io ile Docker image barındırma

  • 🏷️ Semantic Versioning — MAJOR.MINOR.PATCH kuralları

  • 📋 Changelog — her versiyon ne değiştirdiğini belgele

  • 🧹 Temizlik — eski versiyonları otomatik temizle


npm vs GitHub Packages — Ne Zaman Hangisi?

Karar matrisi:
──────────────────────────────────────────────────────
Senaryo                          │ npmjs.com │ GitHub Packages
─────────────────────────────────┼───────────┼────────────────
Herkese açık kütüphane           │ ✅        │ ❌
Şirket içi paylaşım              │ ❌        │ ✅
Organization kapsamlı            │ ❌        │ ✅
Docker image barındırma          │ ❌        │ ✅ (ghcr.io)
Ücretsiz private paket           │ ❌        │ ✅ (sınırlı)
npm ekosistemi uyumu              │ ✅        │ ✅ (scoped)
GitHub Actions entegrasyonu      │ 🟡        │ ✅
Özel domain paket registry       │ ❌        │ ❌ (Artifactory)

💡 İpucu: Küçük ekipler ve startuplar için GitHub Packages mükemmel. Büyük organizasyonlar genellikle Artifactory veya Nexus gibi enterprise registry'ler tercih eder. Başlangıç için GitHub Packages yeterlidir — ihtiyaç büyüdükçe migrate edebilirsiniz.

Bir sonraki derste GitHub Copilot ve AI destekli geliştirme araçlarını keşfedeceğiz.