# Modül Güncellemesi Yayınlama

https://dev.wisecp.com/tr/modul-guncellemesi-yayinlama

Yeni bir sürümü zaten kurmuş sistemlere ulaştırın: tek manifest, tek sürüm, tek operatör kararı.

## Genel Bakış

Sistem günde bir kez ne yayınlandığını sorar; yayıncı sürüm, changelog ve ürün kartıyla yanıt verir. Yeni bir yanıt bildirim açar, güncelleme tıklanınca çalışır.

Bir dizin `manifest.json` taşıyarak güncellenebilir olur; yoksa o kopyaya dokunulmaz.

```json
{"type":"marketplace","id":42,"version":"1.2.0","last_updated":"2026-08-03"}
```

## Ön Koşullar

- **Yayınlanmış bir kayıt**: Tespit kimliğe göre sorar: marketplace sayısal id, store anahtar adı.
- **Yalnız yukarı giden bir sürüm**: PHP sürüm karşılaştırması: `2.10.0`, `2.4.0`'tan yenidir. Yayınlanan numara yeniden kullanılamaz.
- **Müşterinin sunucusunda ZipArchive**: Olmadan çıkarma adımı `zip_unavailable` ile durur.
- **Yazılabilir bir temp dizini**: Her iş `temp/module-update` altında yaşar: arşiv, içerik, geri alma kopyası.
- **İki kez çalışabilen migration'lar**: Güncelleme yolunda size ait hiçbir betik çalıştırılmaz.

## Yapı

Tespit, karar ve kurulum üç ayrı şeydir.

| Aşama | Kim çalıştırır | Ne olur |
| --- | --- | --- |
| Tespit | günlük zamanlanmış görev | Her manifest toplanır ve kaynak başına tek çağrıyla sorulur |
| Bildirim | panel | Sürüm, changelog ve kartın anlık kopyasını taşıyan zil bildirimi; Şimdi Güncelle ve Daha Sonra |
| Sihirbaz | operatör | Dört tekrarlanabilir istek: indir, çıkar, uygula, bitir; token ile adreslenir |

Otomatik kurulum adımı yoktur. Yazdığınız changelog, keşfedildiği gün dondurulur.

> **Changelog'u düzenlemek bekleyen bildirime ulaşmaz**
> 
> Tekilleştirme modül artı sürüm üzerindendir ve okunmamış bildirimlere bakar; zilde duran bildirim metnini korur, sonraki koşu yeni satır açar.

## Adım Adım

### Manifest'i gönderin

`manifest.json` dosyasını sınıf dosyasının yanına, temada `theme.php` ile yan yana koyun.

```bash
coremio/modules/Addons/AuroraBackup/AuroraBackup.php
coremio/modules/Addons/AuroraBackup/config.php
coremio/modules/Addons/AuroraBackup/manifest.json     <- dizini güncellenebilir yapar

templates/website/Aurora/theme.php
templates/website/Aurora/manifest.json                <- theme.php'nin yanında, yerine değil
```

Anlaşılamayan bir manifest yok sayılır: bilinmeyen kaynak, eksik sürüm, id'siz marketplace kaydı, adsız store kaydı.

### Sürümü yükseltin

Karşılaştırılan tek şey manifest'teki sürümdür. Sistem başarılı uygulamadan sonra kendi kopyasını yazar.

```php
// İki taraf da önce normalleştirilir: yalnız ilk satır ve yalnız bir sürümü oluşturan
// karakterler; böylece kaçak bir boşluk ya da bir yorum, hiçbir şeye eşit çıkan bir
// sürüm üretemez.
public static function normalize(string $version): string;
public static function is_newer(string $remote, string $local): bool;

// is_newer('2.10.0', '2.4.0')  === true    version_compare, dize sıralaması değil
// is_newer('1.2.0',  '1.2.0')  === false   eşit olan yeni değildir
// is_newer('1.2.0',  '')       === false   okunamayan taraf asla güncelleme tetiklemez
```

### Arşivi biçimlendirin

Arşiv açılır ve içinde dizininiz aranır; doğru görünen ilk düzen kazanır.

1. Arşivin kökünde, tam olarak modül anahtarı adını taşıyan bir klasör.
2. Tam ağaç: modül için `coremio/modules/{Type}/{Key}`, tema için `templates/website/{Key}`.
3. Kökte adsız tek bir dizin ya da doğru dosyayı taşıyorsa kökün kendisi.

"Doğru görünmek", modül için `{Key}.php`, tema için `theme.php` taşımak demektir; hiçbiri tutmazsa adım `package_mismatch` ile durur.

### Kendi tablolarınızı taşıyın

Güncelleme dosya kopyalar: size ait betik çalışmaz ve enable yolu yeniden çağrılmaz.

```php
public function enable(): bool
{
    // Sıfırdan kurulum yolu.
    $this->check_database();

    return true;
}

public function adminArea(): array
{
    // Yükseltme yolu: enable() yıllar önce, önceki sürümde çoktan koştu.
    $this->check_database();

    // ...
    return ['page_title' => 'Aurora Backup', 'content' => $this->view('index.php')];
}

/**
 * Eksik olanı oluşturur ve sonradan eklenmiş olanı ekler. Her istekte çağrılması
 * güvenlidir: varlık kontrollerinin kendisi migration'dır.
 */
private function check_database(): void
{
    if (!WDB::hasTable('AuroraBackup_jobs')) {
        WDB::exec('CREATE TABLE `AuroraBackup_jobs` (
            `id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
            `user_id` INT UNSIGNED NOT NULL DEFAULT 0,
            `created_at` DATETIME NOT NULL,
            PRIMARY KEY (`id`),
            KEY `owner` (`user_id`)
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci');

        // Her kolon zaten varken oluşturuldu: aşağıdaki hiçbir şeyin koşmasına gerek yok.
        return;
    }

    // 1.2'de eklendi: ondan önce yazılan işler bir zamanlamaya geri izlenemiyordu.
    $col = WDB::query("SHOW COLUMNS FROM `AuroraBackup_jobs` LIKE 'schedule_id'");
    if (!$col || !WDB::getAssoc($col))
        WDB::exec('ALTER TABLE `AuroraBackup_jobs` ADD `schedule_id` INT UNSIGNED NOT NULL DEFAULT 0 AFTER `user_id`');
}
```

Oluşturmayı ve kolon onarımını ayrı dallarda tutun; oluşturma dalındaki tohumlama eski tabloda düşer.

### Changelog'u yazın

Diyalog notları başlıklar altında gruplar, her satır dört tipten birini taşır; bilinmeyen tip en sonda görünür.

```json
[
  {"type": "features",     "text": "Scheduled jobs can now target a second storage account."},
  {"type": "improvements", "text": "Listing a bucket with many objects no longer times out."},
  {"type": "security",     "text": "Restore tokens are now single use."},
  {"type": "fixes",        "text": "A job cancelled during upload left a partial archive behind."}
]
```

## Referans

### Manifest alanları

| Alan | Zorunlu | Kural |
| --- | --- | --- |
| `type` | her zaman | `wstore` ya da `marketplace`; başkası "yönetilmiyor" demektir |
| `id` | marketplace | Sayısal ilan kimliği; dizin adları benzersiz değildir |
| `name` | store | Ürün anahtarı; boşsa manifest'in tamamı reddedilir |
| `version` | her zaman | Kurulu sürüm ve karşılaştırılan tek şey |
| `last_updated` | hayır | Operatöre gösterilir, karara girmez |

### Güncelleyici arayüzü

```php
// diskte olanı okuma
public static function manifest(string $dir): array;
public static function version(string $type, string $key): string;
public static function normalize(string $version): string;
public static function installed(bool $activeOnly = true): array;
public static function dir(array $item): string;

// yayıncıya sorma
public static function releases(array $items): array;
public static function pending(bool $activeOnly = true): array;
public static function issue(string $source, string $ident): array;
public static function notes(array $changelog): array;
public static function is_newer(string $remote, string $local): bool;

// tek bir güncellemeyi çalıştırma
public static function token(string $source, string $ident, string $version): string;
public static function begin(string $source, string $ident, string $version): array;
public static function job(string $token): array;
public static function step(string $token, string $step): array;
public static function abandon(string $token): void;
public static function write_manifest(string $dir, array $manifest): bool;

// STEPS sabit sıradır: download, extract, apply, finish.
// PRESERVED, apply adımının üzerine yazmayı reddettiği dosya adlarının listesidir: config.php.
```

- **ModuleUpdater::installed()**: Yönetilen her dizin: kind, type, key, dir, active ve `ident`.
- **ModuleUpdater::pending()**: Kurulu kayda ek olarak `release`, `product`, `developer`, `changelog`, `available`.
- **ModuleUpdater::manifest()**: Anlam veremediğine boş dizi döner; paketinizi denetlemek için kullanın.
- **ModuleUpdater::step()**: Adlı tek adımı çalıştırır ve aynı adım için tekrar çağrılabilir.
- **ModuleUpdater::token()**: Kaynak, kimlik ve sürümden türer; tekrar deneme yarım işi sürdürür.

### Uygulama ne yapar

- **Önce geri alma kopyası alınır**: Tek bir dosya yazılmadan önce hedef dizinin tamamı bir kenara kopyalanır.
- **Dosyalar birleştirilir, değiştirilmez**: Paket ağacı hedefin üstüne kopyalanır; eksik dizinler oluşturulur.
- **config.php varsa atlanır**: Operatör ayarları korunur; yeni bir bileşenin kendi config'i yine kurulur.
- **Hiçbir şey silinmez**: Paketten çıkardığınız dosya diskte kalır: silme listesi yoktur.
- **Manifest en son yazılır**: Yarıda ölen bir güncelleme bitmiş gibi görünmez.

### Hata kodları

Her adım cümle yerine çıplak bir kod fırlatır, böylece panel onu çevirebilir.

| Kod | Nereden | Genellikle ne demek |
| --- | --- | --- |
| `not_managed` | begin, apply | Okunabilir manifest yok: elle kurulmuş ya da bozuk |
| `already_current` | begin | Sunulan sürüm yeni değil; genellikle yeniden kullanılmış numara |
| `download_failed` | download | İmzalı adres hiçbir şey döndürmedi |
| `zip_unavailable` | extract | Sunucuda ZipArchive yok |
| `archive_unreadable` | extract | Arşiv açılmadı; en sık sebebi iç içe arşiv |
| `package_mismatch` | extract, apply | Hiçbir düzen beklenen sınıf dosyasını taşımıyordu |
| `install_failed` | apply | Kopyalama başarısız; geri alma kopyası yerine kondu |

## Örnek

Tek bir yayın: değişen iki dosyadan sistemin geri okuduğuna kadar.

```json
// müşterinin diskinde olan
{"type": "marketplace", "id": 42, "version": "1.1.0", "last_updated": "2026-05-14"}

// 1.2.0 paketine koyduğunuz
{"type": "marketplace", "id": 42, "version": "1.2.0", "last_updated": "2026-08-03"}
```

```bash
AuroraBackup-1.2.0.zip
 └─ AuroraBackup/                 # tam olarak modül anahtarı
     ├─ AuroraBackup.php          # çıkarıcının aradığı dosya
     ├─ manifest.json             # sürüm 1.2.0
     ├─ config.php                # gönderilir, ama zaten varsa atlanır
     ├─ lang/
     │   └─ en.php                # bir dizin, asla düz bir lang.php değil
     └─ views/
         ├─ index.php
         └─ schedules.php         # 1.2.0'da yeni, düz kopya olarak gelir
```

```php
<?php
// temp/_manifest-probe.php  :  paketin açılmış bir kopyası üzerinde çalıştırın
require dirname(__DIR__) . '/bootstrap.php';

$dir = MODULE_DIR . 'Addons' . DS . 'AuroraBackup';

$manifest = ModuleUpdater::manifest($dir);

// Boş olması, o dizine hiçbir zaman güncelleme sunulmayacağı demektir: bilinmeyen tip,
// eksik sürüm ya da id'siz bir marketplace kaydı.
if (!$manifest) exit("not managed: fix manifest.json\n");

$installed = (string) ($manifest['version'] ?? '');

echo 'reads as: ', $manifest['type'], ' / ', $installed, "\n";
echo ModuleUpdater::is_newer('1.2.0', $installed) ? "1.2.0 would be offered\n" : "1.2.0 would NOT be offered\n";
```

```json
{
    "type": "marketplace",
    "id": 42,
    "version": "1.2.0",
    "last_updated": "2026-08-03"
}
```

Kimlik alanları yeniden türetilmez, orada zaten olandan taşınır.

## Tuzaklar

> **config.php'deki sürümü yükseltmek bir şey değiştirmez**
> 
> Güncelleme o dosyaya hiç yazmaz, oradaki numara ilk kurulumdaki değerde donar.

> **Yanlış seviyeyi ziplemek**
> 
> Kökünde sınıf dosyasını taşıyan arşiv de, modül klasörünü taşıyan da çalışır. Sürüm adlı bir klasör ya da iç içe arşiv package_mismatch ile durur.

> **Enable'daki migration yükseltmeyi atlar**
> 
> Enable bir kez olur ve yükselten her müşteri bunu çoktan yapmıştır, o yüzden oraya bağlanan bir değişiklik mevcut kurulumları atlar.

> **Dosyayı paketten çıkarmak müşteriden çıkarmaz**
> 
> Eski dosya diskte ve yüklenebilir kalır; gitmesi gerekiyorsa yerine gelen sürüm onu etkisizleştirmelidir.

> **Yayınlanmış bir sürüm numarası düzeltilemez**
> 
> Yanlış numara, yerinde düzenlenerek değil, yayın silinip yeniden yayınlanarak düzeltilir.

## İlgili Makaleler

- [Sürüm Yayınlama](https://dev.wisecp.com/tr/pazar-yeri-surum-yayinlama)
- [Modülü Dağıtıma Hazırlama](https://dev.wisecp.com/tr/modulu-dagitima-hazirlama)
- [Modül Lisanslama](https://dev.wisecp.com/tr/modul-lisanslama)
- [Modül Yaşam Döngüsü](https://dev.wisecp.com/tr/modul-yasam-dongusu)
- [Veritabanı Şemasını Değiştirme](https://dev.wisecp.com/tr/veritabani-semasini-degistirme)
- [Çekirdek Yükseltmesini Atlatma](https://dev.wisecp.com/tr/cekirdek-yukseltmesini-atlatma)
