From 0abf181a9cf4b4857ef48dce8e5e6d04f676a798 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sedat=20=C3=96ZT=C3=9CRK?= <76204082+iamsedatozturk@users.noreply.github.com> Date: Thu, 13 Aug 2026 00:26:36 +0300 Subject: [PATCH] =?UTF-8?q?ReleaseNoteAppService=20g=C3=BCncellemesi?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 24 +- api/scripts/generate-release-notes.sh | 86 ++++++ .../ReleaseNotes/ReleaseNoteDto.cs | 14 + .../ReleaseNotes/ReleaseNoteAppService.cs | 46 +++ .../Enums/TableNameEnum.cs | 3 +- .../TableNameResolver.cs | 1 + .../Entities/Host/ReleaseNote.cs | 61 ++++ .../EntityFrameworkCore/PlatformDbContext.cs | 13 + ....cs => 20260812194642_Initial.Designer.cs} | 64 ++++- ...4_Initial.cs => 20260812194642_Initial.cs} | 33 +++ .../PlatformDbContextModelSnapshot.cs | 62 ++++ ...formApplicationConfigurationContributor.cs | 22 +- .../Extensions/ReleaseNoteStartupService.cs | 200 +++++++++++++ .../PlatformHttpApiHostModule.cs | 3 + .../Sozsoft.Platform.HttpApi.Host.csproj | 11 + .../release-notes.json | 230 +++++++++++++++ .../deployment/scripts/4-production-build.sh | 1 - configs/deployment/scripts/build/api.sh | 8 +- configs/deployment/scripts/build/migrator.sh | 4 +- configs/deployment/scripts/build/ui.sh | 15 +- ui/.env | 1 - ui/package.json | 5 +- ui/public/version.json | 234 ---------------- ui/scripts/generate-version.js | 117 -------- ui/scripts/generate-version.sh | 50 ---- ui/src/components/template/Footer.tsx | 2 +- ui/src/components/template/Theme.tsx | 13 +- ui/src/services/releaseNote.service.ts | 26 ++ ui/src/store/abpConfig.model.ts | 5 + ui/src/sw.ts | 26 -- ui/src/views/version/AppUpdateDialog.tsx | 23 +- ui/src/views/version/ChangeLog.tsx | 24 +- ui/src/views/version/swRegistration.ts | 264 +++++------------- ui/src/vite-env.d.ts | 1 - ui/vite.config.ts | 24 -- 35 files changed, 1012 insertions(+), 704 deletions(-) create mode 100644 api/scripts/generate-release-notes.sh create mode 100644 api/src/Sozsoft.Platform.Application.Contracts/ReleaseNotes/ReleaseNoteDto.cs create mode 100644 api/src/Sozsoft.Platform.Application/ReleaseNotes/ReleaseNoteAppService.cs create mode 100644 api/src/Sozsoft.Platform.Domain/Entities/Host/ReleaseNote.cs rename api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/{20260811131124_Initial.Designer.cs => 20260812194642_Initial.Designer.cs} (99%) rename api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/{20260811131124_Initial.cs => 20260812194642_Initial.cs} (99%) create mode 100644 api/src/Sozsoft.Platform.HttpApi.Host/Extensions/ReleaseNoteStartupService.cs create mode 100644 api/src/Sozsoft.Platform.HttpApi.Host/release-notes.json delete mode 100644 ui/public/version.json delete mode 100644 ui/scripts/generate-version.js delete mode 100644 ui/scripts/generate-version.sh create mode 100644 ui/src/services/releaseNote.service.ts diff --git a/README.md b/README.md index 02004c30..7dcee2b5 100644 --- a/README.md +++ b/README.md @@ -693,9 +693,27 @@ konfigürasyonu ile üretilir. ve görseller runtime cache'tedir. - `registerType: 'prompt'` — yeni sürüm indirilir, indirme bitince otomatik devreye alınır (`views/version/swRegistration.ts`, `sw.ts`, `AppUpdateDialog.tsx`). -- `version.json` her zaman ağdan tazelenir (NetworkFirst); changelog `/admin/changeLog` - ekranında gösterilir, yeni deploy sonrası ilk girişte oraya yönlendirilir. -- `scripts/generate-version.js` build sırasında sürüm bilgisini üretir. +- Isıtma yalnızca kuruluma bağlı değildir: uygulama açılışta `ENSURE_WARMUP` mesajı yollar, + service worker eksik kalan bileşenleri kaldığı yerden tamamlar. +- **Sürüm bilgisi backend'dedir.** `Sas_H_ReleaseNote` tablosu tek kaynaktır; istemci sürüm + numarasını ve son sürümün notlarını `application-configuration` yanıtındaki + `extraProperties.changeLogs` alanından okur (`PlatformApplicationConfigurationContributor`), + tam listeyi ise `/api/app/release-note` endpoint'inden alır. Ayrı bir `version.json` isteği + yoktur. Changelog `/admin/changeLog` ekranında gösterilir; sürüm numarası son görülenden + farklıysa kullanıcı giriş sonrası oraya yönlendirilir (tetikleyici bundle değil, sürümdür — + yalnızca yeni bir git tag'i eklenmiş olsa da çalışır). +- **Sürüm notu akışı — DbMigrator gerektirmez:** `api/scripts/generate-release-notes.sh` + build sırasında (`build/api.sh`) git tag'lerinden `HttpApi.Host/release-notes.json` üretir, + dosya API imajına gömülür ve `ReleaseNoteStartupService` uygulama açılışında tabloyu bu + dosyayla eşitler. Yeni sürüm için tag atıp API'yi deploy etmek yeterlidir. Notlar + **annotated** tag mesajından okunur (`git tag -a 1.2.0 -m "..."`), her satır bir madde. +- **Tablo git tag listesinin aynasıdır:** yeni tag eklenir, mesajı düzeltilen tag güncellenir, + git'ten silinen tag tablodan da (hard delete ile) kaldırılır. Bu yüzden `Sas_H_ReleaseNote` + elle düzenlenmemelidir — ilk deploy'da geri alınır. Dosya okunamaz veya boşsa senkronizasyon + hiçbir şey yapmaz, tablo boşaltılmaz. +- Deploy algılaması service worker'ın kendi `registration.update()` kontrolüne dayanır: + herhangi bir chunk değiştiğinde entry hash'i, dolayısıyla index.html ve `sw.js` içindeki + precache manifest'i de değişir. --- diff --git a/api/scripts/generate-release-notes.sh b/api/scripts/generate-release-notes.sh new file mode 100644 index 00000000..e253e6ba --- /dev/null +++ b/api/scripts/generate-release-notes.sh @@ -0,0 +1,86 @@ +#!/bin/bash +set -e + +# Sürüm notlarını git tag'lerinden üretir: +# api/src/Sozsoft.Platform.HttpApi.Host/release-notes.json +# +# Bu dosya API imajına gömülür; uygulama açılışında ReleaseNoteStartupService onu +# `Sas_H_ReleaseNote` tablosuna senkronize eder (yeni tag eklenir, mesajı değişen +# tag güncellenir). Yani YENİ SÜRÜM İÇİN DbMigrator ÇALIŞTIRMAK GEREKMEZ; API'yi +# deploy etmek yeterlidir. İstemci sürüm numarasını ve changelog'u +# application-configuration yanıtındaki `extraProperties.changeLogs` alanından okur. +# +# Deploy akışında build/api.sh tarafından otomatik çağrılır; elle de +# çalıştırılabilir. Git deposu gerektiği için imajın içinde değil, imaj +# oluşturulmadan ÖNCE build sunucusunda koşar. +# +# Dış bağımlılığı yoktur (jq/node gerekmez): JSON kaçışları sed ile yapılır. + +cd "$(dirname "$0")/.." +OUTPUT="src/Sozsoft.Platform.HttpApi.Host/release-notes.json" + +if ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then + echo "> git deposu yok, ${OUTPUT} güncellenmedi." >&2 + exit 0 +fi + +echo "> release-notes.json oluşturuluyor..." + +# origin'de olmayan yerel tag'ler sürüm sırasını bozar; temizle. `--force` şart: +# yerel bir tag origin'dekinden farklı commit'e bakıyorsa git onu "would clobber +# existing tag" diyerek reddediyor ve YENİ tag'ler de alınmamış oluyor. +git fetch --prune --prune-tags --tags --force origin >/dev/null 2>&1 || true + +# Sıra tag adına göre (creatordate değil): 1.1.10 > 1.1.09. +TAGS=$(git tag --list --sort=-v:refname) + +if [ -z "$TAGS" ]; then + echo "> Tag bulunamadı, mevcut dosya korundu." >&2 + exit 0 +fi + +# JSON string kaçışı: önce ters bölü, sonra tırnak, sonra sekme/CR. +escape_json() { + printf '%s' "$1" | sed -e 's/\\/\\\\/g' -e 's/"/\\"/g' -e 's/\t/\\t/g' -e 's/\r//g' +} + +TMP=$(mktemp) +printf '{\n "ReleaseNotes": [\n' > "$TMP" + +FIRST_TAG=true +for TAG in $TAGS; do + VERSION=${TAG#v} + RELEASE_DATE=$(git log -1 --format=%ad --date=short "$TAG") + COMMIT=$(git rev-list -n 1 "$TAG") + + if [ "$FIRST_TAG" = true ]; then FIRST_TAG=false; else printf ',\n' >> "$TMP"; fi + + printf ' {\n' >> "$TMP" + printf ' "version": "%s",\n' "$(escape_json "$VERSION")" >> "$TMP" + printf ' "releaseDate": "%s",\n' "$RELEASE_DATE" >> "$TMP" + printf ' "commit": "%s",\n' "$COMMIT" >> "$TMP" + printf ' "notes": [' >> "$TMP" + + # Annotated tag mesajı; her satır bir madde. Boş satırlar ve baştaki "-" / "*" + # işaretleri temizlenir. Lightweight tag'de mesaj boştur → notes: []. + FIRST_NOTE=true + while IFS= read -r LINE; do + [ -z "$LINE" ] && continue + if [ "$FIRST_NOTE" = true ]; then FIRST_NOTE=false; printf '\n' >> "$TMP"; else printf ',\n' >> "$TMP"; fi + printf ' "%s"' "$(escape_json "$LINE")" >> "$TMP" + done < <(git tag -l --format='%(contents)' "$TAG" \ + | sed -e 's/\r//' -e 's/^[[:space:]]*[-*][[:space:]]*//' -e 's/[[:space:]]*$//' \ + | grep -v '^$' || true) + + if [ "$FIRST_NOTE" = true ]; then + printf ']\n' >> "$TMP" + else + printf '\n ]\n' >> "$TMP" + fi + printf ' }' >> "$TMP" +done + +printf '\n ]\n}\n' >> "$TMP" +mv "$TMP" "$OUTPUT" + +echo "> ${OUTPUT}: $(grep -c '"version":' "$OUTPUT") sürüm" diff --git a/api/src/Sozsoft.Platform.Application.Contracts/ReleaseNotes/ReleaseNoteDto.cs b/api/src/Sozsoft.Platform.Application.Contracts/ReleaseNotes/ReleaseNoteDto.cs new file mode 100644 index 00000000..58acfc15 --- /dev/null +++ b/api/src/Sozsoft.Platform.Application.Contracts/ReleaseNotes/ReleaseNoteDto.cs @@ -0,0 +1,14 @@ +using System; +using Volo.Abp.Application.Dtos; + +namespace Sozsoft.Platform.ReleaseNotes; + +public class ReleaseNoteDto : EntityDto +{ + public string Version { get; set; } + public DateTime ReleaseDate { get; set; } + public string Commit { get; set; } + + /// Sürüm notu maddeleri (entity'de satır satır saklanır). + public string[] ChangeLog { get; set; } +} diff --git a/api/src/Sozsoft.Platform.Application/ReleaseNotes/ReleaseNoteAppService.cs b/api/src/Sozsoft.Platform.Application/ReleaseNotes/ReleaseNoteAppService.cs new file mode 100644 index 00000000..5532b2fc --- /dev/null +++ b/api/src/Sozsoft.Platform.Application/ReleaseNotes/ReleaseNoteAppService.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authorization; +using Sozsoft.Platform.Entities; +using Volo.Abp.Domain.Repositories; + +namespace Sozsoft.Platform.ReleaseNotes; + +/// +/// Sürüm notları (ChangeLog ekranının kaynağı). +/// +/// Kayıtlar git tag'lerinden üretilip Sas_H_ReleaseNote tablosuna +/// tohumlanır. Uygulamanın çalışan sürüm numarası ise application-configuration +/// yanıtındaki extraProperties.changeLogs ile gelir; bu servis yalnızca +/// tam listeyi döner, böylece her config isteğinde tüm changelog taşınmaz. +/// +[Authorize] +public class ReleaseNoteAppService : PlatformAppService +{ + private readonly IRepository _releaseNoteRepository; + + public ReleaseNoteAppService(IRepository releaseNoteRepository) + { + _releaseNoteRepository = releaseNoteRepository; + } + + public async Task> GetListAsync() + { + var items = await _releaseNoteRepository.GetListAsync(x => x.IsPublished); + + return items + .OrderByDescending(note => note.ReleaseDate) + .ThenByDescending(note => note.Version) + .Select(note => new ReleaseNoteDto + { + Id = note.Id, + Version = note.Version, + ReleaseDate = note.ReleaseDate, + Commit = note.Commit, + ChangeLog = note.GetChangeLog() + }) + .ToList(); + } +} diff --git a/api/src/Sozsoft.Platform.Domain.Shared/Enums/TableNameEnum.cs b/api/src/Sozsoft.Platform.Domain.Shared/Enums/TableNameEnum.cs index d8dacd21..0fa3e125 100644 --- a/api/src/Sozsoft.Platform.Domain.Shared/Enums/TableNameEnum.cs +++ b/api/src/Sozsoft.Platform.Domain.Shared/Enums/TableNameEnum.cs @@ -97,5 +97,6 @@ public enum TableNameEnum Sequence, DbTableColumn, DevRequest, - Agenda + Agenda, + ReleaseNote } diff --git a/api/src/Sozsoft.Platform.Domain.Shared/TableNameResolver.cs b/api/src/Sozsoft.Platform.Domain.Shared/TableNameResolver.cs index 80b5aa96..b08469f4 100644 --- a/api/src/Sozsoft.Platform.Domain.Shared/TableNameResolver.cs +++ b/api/src/Sozsoft.Platform.Domain.Shared/TableNameResolver.cs @@ -15,6 +15,7 @@ public static class TableNameResolver { nameof(TableNameEnum.DbTableColumn), (TablePrefix.PlatformByName, MenuPrefix.Saas) }, { nameof(TableNameEnum.LogEntry), (TablePrefix.PlatformByName, MenuPrefix.Saas) }, { nameof(TableNameEnum.AiBot), (TablePrefix.PlatformByName, MenuPrefix.Saas) }, + { nameof(TableNameEnum.ReleaseNote), (TablePrefix.PlatformByName, MenuPrefix.Saas) }, { nameof(TableNameEnum.GlobalSearch), (TablePrefix.TenantByName, MenuPrefix.Saas) }, { nameof(TableNameEnum.ContactTitle), (TablePrefix.PlatformByName, MenuPrefix.Saas) }, { nameof(TableNameEnum.Currency), (TablePrefix.PlatformByName, MenuPrefix.Saas) }, diff --git a/api/src/Sozsoft.Platform.Domain/Entities/Host/ReleaseNote.cs b/api/src/Sozsoft.Platform.Domain/Entities/Host/ReleaseNote.cs new file mode 100644 index 00000000..dabce4d0 --- /dev/null +++ b/api/src/Sozsoft.Platform.Domain/Entities/Host/ReleaseNote.cs @@ -0,0 +1,61 @@ +using System; +using System.Linq; +using Volo.Abp.Domain.Entities.Auditing; + +namespace Sozsoft.Platform.Entities; + +/// +/// Uygulamanın sürüm notları. Tek sürüm kaynağıdır: istemci sürüm numarasını ve +/// changelog'u application-configuration yanıtındaki +/// extraProperties.changeLogs alanından okur, ayrı bir sürüm dosyası yoktur. +/// +/// Kayıtlar git tag'lerinden türer ve açılışta ReleaseNoteStartupService +/// tarafından tag listesiyle birebir eşitlenir. +/// +/// Host seviyesindedir (tenant'a göre değişmez). +/// +public class ReleaseNote : FullAuditedEntity +{ + /// Kullanıcıya gösterilen sürüm numarası (ör. "1.2.1"). + public string Version { get; set; } + + /// Sürümün yayın tarihi; changelog bu alana göre sıralanır. + public DateTime ReleaseDate { get; set; } + + /// İlgili commit (opsiyonel, izlenebilirlik için). + public string Commit { get; set; } + + /// Satır başına bir madde olacak şekilde sürüm notları. + public string Notes { get; set; } + + /// Yayından kaldırılan sürümler listede gösterilmez. + public bool IsPublished { get; set; } + + /// + /// Notları maddelere böler. Tek yerde durmalı: hem changelog endpoint'i hem + /// application-configuration aynı biçimi döner. + /// + public string[] GetChangeLog() + { + return string.IsNullOrWhiteSpace(Notes) + ? [] + : Notes + .Split('\n') + .Select(line => line.Trim().TrimStart('-', '*', ' ')) + .Where(line => !string.IsNullOrWhiteSpace(line)) + .ToArray(); + } + + protected ReleaseNote() + { + } + + public ReleaseNote(string version, DateTime releaseDate, string notes, string commit = null, bool isPublished = true) + { + Version = version; + ReleaseDate = releaseDate; + Notes = notes; + Commit = commit; + IsPublished = isPublished; + } +} diff --git a/api/src/Sozsoft.Platform.EntityFrameworkCore/EntityFrameworkCore/PlatformDbContext.cs b/api/src/Sozsoft.Platform.EntityFrameworkCore/EntityFrameworkCore/PlatformDbContext.cs index 5b681b8e..eb874c85 100644 --- a/api/src/Sozsoft.Platform.EntityFrameworkCore/EntityFrameworkCore/PlatformDbContext.cs +++ b/api/src/Sozsoft.Platform.EntityFrameworkCore/EntityFrameworkCore/PlatformDbContext.cs @@ -46,6 +46,7 @@ public class PlatformDbContext : public DbSet Tenants { get; set; } public DbSet TenantConnectionStrings { get; set; } public DbSet AiBots { get; set; } + public DbSet ReleaseNotes { get; set; } public DbSet Branches { get; set; } public DbSet BranchUsers { get; set; } public DbSet GlobalSearchs { get; set; } @@ -202,6 +203,18 @@ public class PlatformDbContext : b.HasIndex(x => new { x.Name }).IsUnique().HasFilter(null); }); + builder.Entity(b => + { + b.ToTable(TableNameResolver.GetFullTableName(nameof(TableNameEnum.ReleaseNote)), Prefix.DbSchema); + b.ConfigureByConvention(); + + b.Property(a => a.Version).IsRequired().HasMaxLength(32); + b.Property(a => a.Commit).HasMaxLength(64); + b.Property(a => a.Notes).HasColumnType("nvarchar(max)"); + + b.HasIndex(x => new { x.Version }).IsUnique().HasFilter("[IsDeleted] = 0"); + }); + builder.Entity(b => { b.ToTable(TableNameResolver.GetFullTableName(nameof(TableNameEnum.Branch)), Prefix.DbSchema); diff --git a/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260811131124_Initial.Designer.cs b/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260812194642_Initial.Designer.cs similarity index 99% rename from api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260811131124_Initial.Designer.cs rename to api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260812194642_Initial.Designer.cs index e83dbf9c..e2ac5704 100644 --- a/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260811131124_Initial.Designer.cs +++ b/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260812194642_Initial.Designer.cs @@ -13,7 +13,7 @@ using Volo.Abp.EntityFrameworkCore; namespace Sozsoft.Platform.Migrations { [DbContext(typeof(PlatformDbContext))] - [Migration("20260811131124_Initial")] + [Migration("20260812194642_Initial")] partial class Initial { /// @@ -4662,6 +4662,68 @@ namespace Sozsoft.Platform.Migrations b.ToTable("Sas_H_Product", (string)null); }); + modelBuilder.Entity("Sozsoft.Platform.Entities.ReleaseNote", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier"); + + b.Property("Commit") + .HasMaxLength(64) + .HasColumnType("nvarchar(64)"); + + b.Property("CreationTime") + .HasColumnType("datetime2") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uniqueidentifier") + .HasColumnName("CreatorId"); + + b.Property("DeleterId") + .HasColumnType("uniqueidentifier") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("datetime2") + .HasColumnName("DeletionTime"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("IsPublished") + .HasColumnType("bit"); + + b.Property("LastModificationTime") + .HasColumnType("datetime2") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uniqueidentifier") + .HasColumnName("LastModifierId"); + + b.Property("Notes") + .HasColumnType("nvarchar(max)"); + + b.Property("ReleaseDate") + .HasColumnType("datetime2"); + + b.Property("Version") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("nvarchar(32)"); + + b.HasKey("Id"); + + b.HasIndex("Version") + .IsUnique() + .HasFilter("[IsDeleted] = 0"); + + b.ToTable("Sas_H_ReleaseNote", (string)null); + }); + modelBuilder.Entity("Sozsoft.Platform.Entities.ReportCategory", b => { b.Property("Id") diff --git a/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260811131124_Initial.cs b/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260812194642_Initial.cs similarity index 99% rename from api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260811131124_Initial.cs rename to api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260812194642_Initial.cs index 44fca048..8a9b76ea 100644 --- a/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260811131124_Initial.cs +++ b/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260812194642_Initial.cs @@ -1700,6 +1700,29 @@ namespace Sozsoft.Platform.Migrations table.PrimaryKey("PK_Sas_H_Product", x => x.Id); }); + migrationBuilder.CreateTable( + name: "Sas_H_ReleaseNote", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + Version = table.Column(type: "nvarchar(32)", maxLength: 32, nullable: false), + ReleaseDate = table.Column(type: "datetime2", nullable: false), + Commit = table.Column(type: "nvarchar(64)", maxLength: 64, nullable: true), + Notes = table.Column(type: "nvarchar(max)", nullable: true), + IsPublished = table.Column(type: "bit", nullable: false), + CreationTime = table.Column(type: "datetime2", nullable: false), + CreatorId = table.Column(type: "uniqueidentifier", nullable: true), + LastModificationTime = table.Column(type: "datetime2", nullable: true), + LastModifierId = table.Column(type: "uniqueidentifier", nullable: true), + IsDeleted = table.Column(type: "bit", nullable: false, defaultValue: false), + DeleterId = table.Column(type: "uniqueidentifier", nullable: true), + DeletionTime = table.Column(type: "datetime2", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Sas_H_ReleaseNote", x => x.Id); + }); + migrationBuilder.CreateTable( name: "Sas_H_Route", columns: table => new @@ -4234,6 +4257,13 @@ namespace Sozsoft.Platform.Migrations unique: true, filter: "[IsDeleted] = 0"); + migrationBuilder.CreateIndex( + name: "IX_Sas_H_ReleaseNote_Version", + table: "Sas_H_ReleaseNote", + column: "Version", + unique: true, + filter: "[IsDeleted] = 0"); + migrationBuilder.CreateIndex( name: "IX_Sas_H_Route_Key", table: "Sas_H_Route", @@ -4574,6 +4604,9 @@ namespace Sozsoft.Platform.Migrations migrationBuilder.DropTable( name: "Sas_H_Product"); + migrationBuilder.DropTable( + name: "Sas_H_ReleaseNote"); + migrationBuilder.DropTable( name: "Sas_H_Route"); diff --git a/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/PlatformDbContextModelSnapshot.cs b/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/PlatformDbContextModelSnapshot.cs index 91bb9bd5..049191ad 100644 --- a/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/PlatformDbContextModelSnapshot.cs +++ b/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/PlatformDbContextModelSnapshot.cs @@ -4659,6 +4659,68 @@ namespace Sozsoft.Platform.Migrations b.ToTable("Sas_H_Product", (string)null); }); + modelBuilder.Entity("Sozsoft.Platform.Entities.ReleaseNote", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier"); + + b.Property("Commit") + .HasMaxLength(64) + .HasColumnType("nvarchar(64)"); + + b.Property("CreationTime") + .HasColumnType("datetime2") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uniqueidentifier") + .HasColumnName("CreatorId"); + + b.Property("DeleterId") + .HasColumnType("uniqueidentifier") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("datetime2") + .HasColumnName("DeletionTime"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("IsPublished") + .HasColumnType("bit"); + + b.Property("LastModificationTime") + .HasColumnType("datetime2") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uniqueidentifier") + .HasColumnName("LastModifierId"); + + b.Property("Notes") + .HasColumnType("nvarchar(max)"); + + b.Property("ReleaseDate") + .HasColumnType("datetime2"); + + b.Property("Version") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("nvarchar(32)"); + + b.HasKey("Id"); + + b.HasIndex("Version") + .IsUnique() + .HasFilter("[IsDeleted] = 0"); + + b.ToTable("Sas_H_ReleaseNote", (string)null); + }); + modelBuilder.Entity("Sozsoft.Platform.Entities.ReportCategory", b => { b.Property("Id") diff --git a/api/src/Sozsoft.Platform.HttpApi.Host/Extensions/PlatformApplicationConfigurationContributor.cs b/api/src/Sozsoft.Platform.HttpApi.Host/Extensions/PlatformApplicationConfigurationContributor.cs index bd38a098..5c6f7722 100644 --- a/api/src/Sozsoft.Platform.HttpApi.Host/Extensions/PlatformApplicationConfigurationContributor.cs +++ b/api/src/Sozsoft.Platform.HttpApi.Host/Extensions/PlatformApplicationConfigurationContributor.cs @@ -23,7 +23,26 @@ public class PlatformApplicationConfigurationContributor : IApplicationConfigura context.ApplicationConfiguration.SetProperty("environment", hostEnvironment.EnvironmentName); var configuration = context.ServiceProvider.GetRequiredService(); - context.ApplicationConfiguration.SetProperty("version", configuration.GetValue("App:Version")); + context.ApplicationConfiguration.SetProperty("apiVersion", configuration.GetValue("App:Version")); + + // Uygulamanın çalışan sürümü: istemcinin tek sürüm kaynağı (ayrı bir + // version.json isteği yoktur). Yalnızca EN SON sürüm taşınır; tam liste + // ChangeLog ekranının kendi endpoint'inden (/api/app/release-note) gelir, + // aksi halde her config isteğinde bütün changelog gönderilirdi. + var releaseNoteRepository = context.ServiceProvider.GetRequiredService>(); + var releaseNotes = await releaseNoteRepository.GetListAsync(x => x.IsPublished); + + var latest = releaseNotes + .OrderByDescending(note => note.ReleaseDate) + .ThenByDescending(note => note.Version) + .FirstOrDefault(); + + context.ApplicationConfiguration.SetProperty("changeLogs", new + { + uiVersion = latest?.Version, + buildDate = latest?.ReleaseDate.ToString("yyyy-MM-dd"), + tags = latest?.GetChangeLog() ?? [] + }); // GetListAsync boş liste döner, null dönmez — bu yüzden null kontrolü yok. // Veritabanı erişilemezse çağrı exception atar; bu durum istek boru hattında, @@ -49,5 +68,6 @@ public class PlatformApplicationConfigurationContributor : IApplicationConfigura context.ApplicationConfiguration.SetProperty("currentUserSessionId", sessionId); } } + } diff --git a/api/src/Sozsoft.Platform.HttpApi.Host/Extensions/ReleaseNoteStartupService.cs b/api/src/Sozsoft.Platform.HttpApi.Host/Extensions/ReleaseNoteStartupService.cs new file mode 100644 index 00000000..42b63910 --- /dev/null +++ b/api/src/Sozsoft.Platform.HttpApi.Host/Extensions/ReleaseNoteStartupService.cs @@ -0,0 +1,200 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Text.Json; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using Sozsoft.Platform.Entities; +using Volo.Abp.Domain.Repositories; +using Volo.Abp.MultiTenancy; +using Volo.Abp.Uow; + +namespace Sozsoft.Platform.Extensions; + +public class ReleaseNoteSeedDto +{ + public string Version { get; set; } + public string ReleaseDate { get; set; } + public string Commit { get; set; } + public List Notes { get; set; } +} + +public class ReleaseNoteSeedFileDto +{ + public List ReleaseNotes { get; set; } +} + +/// +/// Sürüm notlarını uygulama açılışında veritabanına senkronize eder. +/// +/// Kaynak, imaja gömülen release-notes.json dosyasıdır; bu dosya build +/// sırasında api/scripts/generate-release-notes.sh tarafından git +/// tag'lerinden üretilir. Böylece yeni bir sürüm tag'i eklemek için DbMigrator +/// çalıştırmak GEREKMEZ — API'yi deploy etmek yeterlidir. +/// +/// Tablo git tag listesinin birebir aynadır: yeni tag eklenir, mesajı düzeltilen +/// tag güncellenir, silinen tag veritabanından da kaldırılır. Dosya okunamaz veya +/// boşsa hiçbir şey yapılmaz (yanlışlıkla tablo boşaltılmasın diye). +/// +public class ReleaseNoteStartupService : IHostedService +{ + private const string FileName = "release-notes.json"; + + private readonly IServiceProvider _serviceProvider; + private readonly IHostApplicationLifetime _applicationLifetime; + private readonly IHostEnvironment _environment; + private readonly ILogger _logger; + + public ReleaseNoteStartupService( + IServiceProvider serviceProvider, + IHostApplicationLifetime applicationLifetime, + IHostEnvironment environment, + ILogger logger) + { + _serviceProvider = serviceProvider; + _applicationLifetime = applicationLifetime; + _environment = environment; + _logger = logger; + } + + public Task StartAsync(CancellationToken cancellationToken) + { + _applicationLifetime.ApplicationStarted.Register(() => + { + _ = Task.Run(SyncAsync); + }); + + return Task.CompletedTask; + } + + public Task StopAsync(CancellationToken cancellationToken) + { + return Task.CompletedTask; + } + + private async Task SyncAsync() + { + var path = Path.Combine(_environment.ContentRootPath, FileName); + + if (!File.Exists(path)) + { + _logger.LogWarning("{FileName} bulunamadı, sürüm notları senkronize edilmedi.", FileName); + return; + } + + // Veritabanı henüz hazır olmayabilir (kurulum modu, migration sürüyor). + const int maxAttempts = 5; + + for (var attempt = 1; attempt <= maxAttempts; attempt++) + { + try + { + await using var stream = File.OpenRead(path); + var data = await JsonSerializer.DeserializeAsync( + stream, + new JsonSerializerOptions { PropertyNameCaseInsensitive = true }); + + var items = data?.ReleaseNotes ?? []; + if (items.Count == 0) + { + return; + } + + using var scope = _serviceProvider.CreateScope(); + var (inserted, updated, deleted) = await SyncItemsAsync(scope.ServiceProvider, items); + + _logger.LogInformation( + "Sürüm notları senkronize edildi: {Total} tag, {Inserted} eklendi, {Updated} güncellendi, {Deleted} silindi.", + items.Count, + inserted, + updated, + deleted); + return; + } + catch (Exception ex) + { + if (attempt == maxAttempts) + { + _logger.LogError(ex, "Sürüm notları senkronize edilemedi."); + return; + } + + _logger.LogWarning(ex, "Sürüm notları senkronizasyonu başarısız, yeniden denenecek."); + await Task.Delay(TimeSpan.FromSeconds(3)); + } + } + } + + private static async Task<(int Inserted, int Updated, int Deleted)> SyncItemsAsync( + IServiceProvider services, + List items) + { + var repository = services.GetRequiredService>(); + var unitOfWorkManager = services.GetRequiredService(); + var currentTenant = services.GetRequiredService(); + + // Tablo host seviyesindedir; istek bağlamı olmadığı için tenant açıkça boş. + using (currentTenant.Change(null)) + using (var uow = unitOfWorkManager.Begin(requiresNew: true)) + { + var existingList = await repository.GetListAsync(); + var existing = existingList.ToDictionary(note => note.Version, StringComparer.OrdinalIgnoreCase); + var tags = new HashSet(StringComparer.OrdinalIgnoreCase); + var inserted = 0; + var updated = 0; + + foreach (var item in items.Where(item => !string.IsNullOrWhiteSpace(item.Version))) + { + tags.Add(item.Version); + + var releaseDate = ParseDate(item.ReleaseDate); + var notes = string.Join(Environment.NewLine, item.Notes ?? []); + + if (!existing.TryGetValue(item.Version, out var current)) + { + await repository.InsertAsync(new ReleaseNote(item.Version, releaseDate, notes, item.Commit)); + inserted++; + continue; + } + + // Tag mesajı sonradan düzeltilmiş olabilir; yalnızca gerçekten + // değişen kayıt yazılır. + if (current.Notes == notes && current.ReleaseDate == releaseDate && current.Commit == item.Commit) + { + continue; + } + + current.Notes = notes; + current.ReleaseDate = releaseDate; + current.Commit = item.Commit; + + await repository.UpdateAsync(current); + updated++; + } + + // Git'ten silinen tag'ler tabloda kalmamalı. Soft delete değil hard + // delete: aynı sürüm numarası tekrar tag'lenirse Version üzerindeki + // benzersiz indeks silinmiş satıra takılmasın. + var orphans = existingList.Where(note => !tags.Contains(note.Version)).ToList(); + if (orphans.Count > 0) + { + await repository.HardDeleteAsync(orphans); + } + + await uow.CompleteAsync(); + return (inserted, updated, orphans.Count); + } + } + + private static DateTime ParseDate(string value) + { + return DateTime.TryParse(value, CultureInfo.InvariantCulture, DateTimeStyles.None, out var parsed) + ? parsed + : DateTime.UtcNow; + } +} diff --git a/api/src/Sozsoft.Platform.HttpApi.Host/PlatformHttpApiHostModule.cs b/api/src/Sozsoft.Platform.HttpApi.Host/PlatformHttpApiHostModule.cs index a1facbf8..1c3612fe 100644 --- a/api/src/Sozsoft.Platform.HttpApi.Host/PlatformHttpApiHostModule.cs +++ b/api/src/Sozsoft.Platform.HttpApi.Host/PlatformHttpApiHostModule.cs @@ -140,6 +140,9 @@ public class PlatformHttpApiHostModule : AbpModule ConfigureCache(); ConfigureHangfire(context, configuration); context.Services.AddHostedService(); + // Sürüm notları imaja gömülü release-notes.json'dan senkronize edilir; + // yeni bir sürüm tag'i için DbMigrator çalıştırmak gerekmez. + context.Services.AddHostedService(); ConfigureBlobStoring(configuration); ConfigureAuditing(); diff --git a/api/src/Sozsoft.Platform.HttpApi.Host/Sozsoft.Platform.HttpApi.Host.csproj b/api/src/Sozsoft.Platform.HttpApi.Host/Sozsoft.Platform.HttpApi.Host.csproj index 023e1bd2..8c181d05 100644 --- a/api/src/Sozsoft.Platform.HttpApi.Host/Sozsoft.Platform.HttpApi.Host.csproj +++ b/api/src/Sozsoft.Platform.HttpApi.Host/Sozsoft.Platform.HttpApi.Host.csproj @@ -45,5 +45,16 @@ + + + + + Always + Always + + + diff --git a/api/src/Sozsoft.Platform.HttpApi.Host/release-notes.json b/api/src/Sozsoft.Platform.HttpApi.Host/release-notes.json new file mode 100644 index 00000000..5ff35f57 --- /dev/null +++ b/api/src/Sozsoft.Platform.HttpApi.Host/release-notes.json @@ -0,0 +1,230 @@ +{ + "ReleaseNotes": [ + { + "version": "1.2.0", + "releaseDate": "2026-08-13", + "commit": "798acbef9e6a26e29e58f10b13cb8e63ac5fa6b4", + "notes": [ + "Claude.ai optimizasyon güncellemesi", + "Migrations ve Seeds klasör düzenlemesi", + "New Version Check komponent düzenlemesi" + ] + }, + { + "version": "1.1.10", + "releaseDate": "2026-07-31", + "commit": "2eef3ead35848341350659828a14dc4a463e57af", + "notes": [ + "Kullanıcılara User Type sütunu eklendi. Hem \"İç Kullanıcılar\" hemde \"Dış Kullanıcılar\" tanımlanabiliyor.", + "Lisans adedi sadece İç Kullanıcılar üzerinden çalışıyor.", + "Scheduler ve TodoBard komponentler geliştirildi.", + "Lookup editorlerin Static değerlere dil desteği eklendi." + ] + }, + { + "version": "1.1.09", + "releaseDate": "2026-07-29", + "commit": "b0e78ac619dfebac2eb17edfc04551217ca4b93e", + "notes": [ + "Wizard yetenekleri artırıldı. Menu Order eklendi.", + "GanttView sample düzenlemeleri", + "Schedulerview sample düzenlemeleri", + "CryptographyXml version 10.0.10" + ] + }, + { + "version": "1.1.08", + "releaseDate": "2026-07-16", + "commit": "24e92fcb82eef0ee26ca5d65e08c5404f132a09e", + "notes": [ + "CardView komponenti geliştirildi.", + "Todo / Kanban komponenti geliştirildi.", + "Licence information", + "Check New Versiyon komponenti." + ] + }, + { + "version": "1.1.07", + "releaseDate": "2026-07-13", + "commit": "50cb3eaf979eb5027172a92a4e941ed64aa3671e", + "notes": [ + "localStorage tüm bilgiler easy-peasy içerisinde birleştirildi.", + "Codex 5.6 sol optimizasyon yapıldı. Bir çok komponent Lazy load ile yüklendi.", + "Workflow komponentinden birden fazla kişi onay verebilir.", + "Posta Kodu eklendi.", + "Sales Order kısmında Yeni ve Eski müşteri sipariş ayrımı yapıldı" + ] + }, + { + "version": "1.1.06", + "releaseDate": "2026-07-06", + "commit": "bab639ce6343b451b81d6688e4f7af666f0233da", + "notes": [ + "Çekirdek kod için optimizasyon", + "List Reportlara Gruplu ve Alttoplam eklendi.", + "Paketlerdeki deprecated uyarları kaldırıldı" + ] + }, + { + "version": "1.1.05", + "releaseDate": "2026-06-27", + "commit": "352cf047b0af1b8aad53b632a41161efcc6cecdf", + "notes": [ + "Ai için Ollama kurulumu yapıldı. Ayrıca Gemma LLM ile çalıştırıldı.", + "DynamicReport eklendi. ReportDesigner ile çalışan bir rapor tasarlandı.", + "DynamicGridReport eklendi. Grid üzerinde otomatik rapor alınabiliyor.", + "DynamicTreeReport eklendi. Tree üzerinden otomatik rapor alınabiliyor.", + "DynamicFormReport eklendi. Single ekranlarda otomatik rapor eklendi.", + "Demo sayfası menüden ayrıldı." + ] + }, + { + "version": "1.1.04", + "releaseDate": "2026-06-04", + "commit": "20e7fae481ce69e9a678508ce03b5ed7831aea9f", + "notes": [ + "Settingde yapılan ayarlar Auth komponentlerine uygulandı.", + "Public home ve diğer sayfaların tasarım değişikliği yapıldı.", + "Route Type Dinamik ve Normal olarak ayrıldı.", + "Form Devexpress DefaultValue özelliği eklendi.", + "Devexpress DarkModa uygun şekilde güncellendi.", + "Grid, Tree ve FormDevexpress setReadonly özelliği eklendi." + ] + }, + { + "version": "1.1.03", + "releaseDate": "2026-05-30", + "commit": "96f7091d46c248ba3c42849fe5d870db0ab96982", + "notes": [ + "User Detail komponentinin içerisinde Avatar ekleme", + "EditForm un içerisinde EditorOptions dinamik oluşturulması", + "Editform un içerisinde EditorScript dinamik oluşturulması" + ] + }, + { + "version": "1.1.02", + "releaseDate": "2026-05-27", + "commit": "84b9f6510787bd82f3797728fd675f755b4caa2d", + "notes": [ + ".NET 10 yükseltildi.", + "Abp Framework 10 yükseltildi.", + "Sql Query Manager problemleri giderildi.", + "AuditLog problemleri giderildi.", + "Tenan yapısını uygunluğu için düzenlemeler yapıldı." + ] + }, + { + "version": "1.1.01", + "releaseDate": "2026-05-24", + "commit": "6262baa6f12d695a25d83304af985092715d439a", + "notes": [ + "Workflow tanımlaması yapılabilir.", + "Dark mod için uygulama güncellemesi", + "Form Field kısmında düzenleme", + "Hangfire Recurring Job düzenlemesi." + ] + }, + { + "version": "1.0.10", + "releaseDate": "2026-05-11", + "commit": "414006204e324018be597a598d3dd102868c5cca", + "notes": [ + "Backup dosyalarını son 5 günün kalması", + "Grid için Fit Columns özelliği eklendi.", + "Notification Desktop, UiActivity, UiToast özelliği eklendi", + "Versiyon güncellemeleri için \"System Updating\" mesajı" + ] + }, + { + "version": "1.0.9", + "releaseDate": "2026-05-09", + "commit": "e9d8f5ebac35fc7b21f08139dfe175ed548c14b4", + "notes": [ + "Video Rooms düzenlemesi", + "Genel Liste düzenlemeleri" + ] + }, + { + "version": "1.0.8", + "releaseDate": "2026-05-07", + "commit": "6fa266f23ed4f67384b7bef0e4030420cb788af5", + "notes": [ + "Intranet ve Sosyal Duvar", + "Listelerde ImageUploadandViewer", + "Survey Widget", + "Event, Category ve Type" + ] + }, + { + "version": "1.0.7", + "releaseDate": "2026-05-04", + "commit": "4444fce93b4f62ad6698099fefe7da1614d2859c", + "notes": [ + "Departman tanımlama", + "Job Position tanımlama", + "Wizard ve SQl Data güncellemeleri", + "Organizasyon Şemanı eklendi" + ] + }, + { + "version": "1.0.6", + "releaseDate": "2026-05-03", + "commit": "b2dfb04879a93709b04f8f0fc3bd4f5af1f7c01b", + "notes": [ + "SalesOrder ve SalesOrderItem listFormu tanımlandı.", + "Sql Backup dosyalarını File Manager içerisinde görüntülenebiliyor.", + "Wizard File Manager komponenti oluşturuldu.", + "Wizard komponentini Edit özelliği eklendi." + ] + }, + { + "version": "1.0.5", + "releaseDate": "2026-04-28", + "commit": "b9cc68ff41d675fefadab79f5ea2c5e7f376708a", + "notes": [ + "AllowAdding özelliği eklendi.", + "Kulllanıcı bazında Workhour özelliği eklendi.", + "ConcurrentUser sınırlaması getirildi.", + "Uygulamadan kullanıcıyı kickleme özelliği getirildi.", + "Uygulama açıkken DbMigrator çağrısı yapıldı." + ] + }, + { + "version": "1.0.4", + "releaseDate": "2026-03-30", + "commit": "e9ce256c0706de408e08a462bee9e4a72653f2c7", + "notes": [ + "Role Yetkilerini kopyalama", + "Place Holder güncellemesi", + "Kayıt kopyalama özelliği" + ] + }, + { + "version": "1.0.3", + "releaseDate": "2026-03-28", + "commit": "130d35c37703935e34948419f2ee900cc1b6b28c", + "notes": [ + "Report Template View And Design" + ] + }, + { + "version": "1.0.2", + "releaseDate": "2026-03-21", + "commit": "196b0dc24b7a815586166679b58ab4f9334bd40e", + "notes": [ + "Artificial Intelligence Chat Bot güncellemeleri" + ] + }, + { + "version": "1.0.1", + "releaseDate": "2026-03-18", + "commit": "ac7784c030825158641e9042c50dede2067c0486", + "notes": [ + "Public Site Designer", + "Menu Groups", + "Sql Query Manager", + "Listform Wizard" + ] + } + ] +} diff --git a/configs/deployment/scripts/4-production-build.sh b/configs/deployment/scripts/4-production-build.sh index bf559ce7..298ab189 100644 --- a/configs/deployment/scripts/4-production-build.sh +++ b/configs/deployment/scripts/4-production-build.sh @@ -2,7 +2,6 @@ set -e cd ~/sozsoft-platform -git restore ui/public/version.json 2>/dev/null || git checkout -- ui/public/version.json ./configs/deployment/scripts/build/api.sh ./configs/deployment/scripts/build/migrator.sh diff --git a/configs/deployment/scripts/build/api.sh b/configs/deployment/scripts/build/api.sh index 4e1bd909..4e071e07 100644 --- a/configs/deployment/scripts/build/api.sh +++ b/configs/deployment/scripts/build/api.sh @@ -1,8 +1,14 @@ cd ~/sozsoft-platform git checkout main -git fetch && git pull +# --tags --force: sürüm notları git tag'lerinden üretiliyor; yerel tag origin'den +# farklı commit'e bakıyorsa --force olmadan reddedilir ve yeni tag'ler gelmez. +git fetch --prune --prune-tags --tags --force && git pull cd ~/sozsoft-platform/api + +# Sürüm notları git tag'lerinden üretilir ve API imajına gömülür; uygulama +# açılışta bunları veritabanına senkronize eder (DbMigrator gerekmez). +./scripts/generate-release-notes.sh VERSION=$(cat ~/sozsoft-platform/api/src/Sozsoft.Platform.HttpApi.Host/appsettings.json \ | grep -o '"[^"]*"\s*:\s*"[^"]*"' \ | grep -E '^"(Version)"' \ diff --git a/configs/deployment/scripts/build/migrator.sh b/configs/deployment/scripts/build/migrator.sh index 5a3bf846..3fb8bb80 100644 --- a/configs/deployment/scripts/build/migrator.sh +++ b/configs/deployment/scripts/build/migrator.sh @@ -1,6 +1,8 @@ cd ~/sozsoft-platform git checkout main -git fetch && git pull +# --tags --force: sürüm notları git tag'lerinden üretiliyor; yerel tag origin'den +# farklı commit'e bakıyorsa --force olmadan reddedilir ve yeni tag'ler gelmez. +git fetch --prune --prune-tags --tags --force && git pull cd ~/sozsoft-platform/api diff --git a/configs/deployment/scripts/build/ui.sh b/configs/deployment/scripts/build/ui.sh index ca5ad684..48598dfa 100644 --- a/configs/deployment/scripts/build/ui.sh +++ b/configs/deployment/scripts/build/ui.sh @@ -7,19 +7,20 @@ git checkout main # --prune-tags: sunucuda elle atılmış, origin'de olmayan yerel tag'leri siler. # Bunlar temizlenmezse `git tag` listesinde kalır ve deploy yanlış (daha yüksek) # bir sürüm numarasıyla etiketlenir. -git fetch --prune --prune-tags --tags && git pull +git fetch --prune --prune-tags --tags --force && git pull cd ~/sozsoft-platform/ui -# Sürüm, HEAD'den erişilebilen son tag'dir (başka daldaki tag'ler sayılmaz); -# tag yoksa package.json'daki sürüm kullanılır. +# Yalnızca imaj etiketi: HEAD'den erişilebilen son tag (başka daldaki tag'ler +# sayılmaz), tag yoksa kısa commit. Uygulamanın gösterdiği sürüm buradan değil, +# backend'deki sürüm notları tablosundan gelir. VERSION=$(git describe --tags --abbrev=0 2>/dev/null | sed 's/^v//') -VERSION=${VERSION:-$(jq -r .version package.json)} +VERSION=${VERSION:-$(git rev-parse --short HEAD)} echo "Build versiyonu: ${VERSION}" -# Deploy öncesi git üzerindeki tüm versiyon bilgilerini güncelle -./scripts/generate-version.sh - +# Sürüm numarası ve sürüm notları backend'de (Sürüm Notları tablosu) tutulur; +# UI tarafında üretilecek bir sürüm dosyası yoktur. Buradaki VERSION yalnızca +# imaj etiketidir. docker build \ -t devops.sozsoft.com/sozsoft/sozsoft-platform-ui:${ENV}-${VERSION} \ -t devops.sozsoft.com/sozsoft/sozsoft-platform-ui:${ENV}-latest \ diff --git a/ui/.env b/ui/.env index ed93409f..1e1983de 100644 --- a/ui/.env +++ b/ui/.env @@ -1,4 +1,3 @@ VITE_API_URL='https://localhost:44344' VITE_CDN_URL='http://localhost:4005' -VITE_REACT_APP_VERSION=$npm_package_version VITE_AI_URL='https://ai.sozsoft.com/webhook/' diff --git a/ui/package.json b/ui/package.json index 4300e350..1027f721 100644 --- a/ui/package.json +++ b/ui/package.json @@ -1,13 +1,12 @@ { "name": "sozsoft-platform-ui", "private": true, - "version": "1.0.7", "elstarVersion": "2.1.6", "type": "module", "scripts": { "start": "vite", - "build": "node scripts/generate-version.js && vite build", - "build:production": "node scripts/generate-version.js && vite build", + "build": "vite build", + "build:production": "vite build", "preview": "vite preview", "lint": "eslint .", "lint:fix": "npm run lint -- --fix", diff --git a/ui/public/version.json b/ui/public/version.json deleted file mode 100644 index b3af35d1..00000000 --- a/ui/public/version.json +++ /dev/null @@ -1,234 +0,0 @@ -{ - "buildId": "98e9f84.msq55max", - "buildTime": "2026-08-12T13:44:50.404Z", - "buildDate": "2026-08-12", - "commit": "98e9f84", - "packageVersion": "1.0.7", - "version": "1.2.1", - "releases": [ - { - "version": "1.2.1", - "buildDate": "2026-08-11", - "commit": "783c1fe1d35f7cb8f23fb63fad26123cbe54aafc", - "changeLog": [ - "Claude.ai güncellemesi" - ] - }, - { - "version": "1.1.10", - "buildDate": "2026-07-31", - "commit": "2eef3ead35848341350659828a14dc4a463e57af", - "changeLog": [ - "Kullanıcılara User Type sütunu eklendi. Hem \"İç Kullanıcılar\" hemde \"Dış Kullanıcılar\" tanımlanabiliyor.", - "Lisans adedi sadece İç Kullanıcılar üzerinden çalışıyor.", - "Scheduler ve TodoBard komponentler geliştirildi.", - "Lookup editorlerin Static değerlere dil desteği eklendi." - ] - }, - { - "version": "1.1.09", - "buildDate": "2026-07-29", - "commit": "b0e78ac619dfebac2eb17edfc04551217ca4b93e", - "changeLog": [ - "Wizard yetenekleri artırıldı. Menu Order eklendi.", - "GanttView sample düzenlemeleri", - "Schedulerview sample düzenlemeleri", - "CryptographyXml version 10.0.10" - ] - }, - { - "version": "1.1.08", - "buildDate": "2026-07-16", - "commit": "24e92fcb82eef0ee26ca5d65e08c5404f132a09e", - "changeLog": [ - "CardView komponenti geliştirildi.", - "Todo / Kanban komponenti geliştirildi.", - "Licence information", - "Check New Versiyon komponenti." - ] - }, - { - "version": "1.1.07", - "buildDate": "2026-07-13", - "commit": "50cb3eaf979eb5027172a92a4e941ed64aa3671e", - "changeLog": [ - "localStorage tüm bilgiler easy-peasy içerisinde birleştirildi.", - "Codex 5.6 sol optimizasyon yapıldı. Bir çok komponent Lazy load ile yüklendi.", - "Workflow komponentinden birden fazla kişi onay verebilir.", - "Posta Kodu eklendi.", - "Sales Order kısmında Yeni ve Eski müşteri sipariş ayrımı yapıldı" - ] - }, - { - "version": "1.1.06", - "buildDate": "2026-07-06", - "commit": "bab639ce6343b451b81d6688e4f7af666f0233da", - "changeLog": [ - "Çekirdek kod için optimizasyon", - "List Reportlara Gruplu ve Alttoplam eklendi.", - "Paketlerdeki deprecated uyarları kaldırıldı" - ] - }, - { - "version": "1.1.05", - "buildDate": "2026-06-27", - "commit": "352cf047b0af1b8aad53b632a41161efcc6cecdf", - "changeLog": [ - "Ai için Ollama kurulumu yapıldı. Ayrıca Gemma LLM ile çalıştırıldı.", - "DynamicReport eklendi. ReportDesigner ile çalışan bir rapor tasarlandı.", - "DynamicGridReport eklendi. Grid üzerinde otomatik rapor alınabiliyor.", - "DynamicTreeReport eklendi. Tree üzerinden otomatik rapor alınabiliyor.", - "DynamicFormReport eklendi. Single ekranlarda otomatik rapor eklendi.", - "Demo sayfası menüden ayrıldı." - ] - }, - { - "version": "1.1.04", - "buildDate": "2026-06-04", - "commit": "20e7fae481ce69e9a678508ce03b5ed7831aea9f", - "changeLog": [ - "Settingde yapılan ayarlar Auth komponentlerine uygulandı.", - "Public home ve diğer sayfaların tasarım değişikliği yapıldı.", - "Route Type Dinamik ve Normal olarak ayrıldı.", - "Form Devexpress DefaultValue özelliği eklendi.", - "Devexpress DarkModa uygun şekilde güncellendi.", - "Grid, Tree ve FormDevexpress setReadonly özelliği eklendi." - ] - }, - { - "version": "1.1.03", - "buildDate": "2026-05-30", - "commit": "96f7091d46c248ba3c42849fe5d870db0ab96982", - "changeLog": [ - "User Detail komponentinin içerisinde Avatar ekleme", - "EditForm un içerisinde EditorOptions dinamik oluşturulması", - "Editform un içerisinde EditorScript dinamik oluşturulması" - ] - }, - { - "version": "1.1.02", - "buildDate": "2026-05-27", - "commit": "84b9f6510787bd82f3797728fd675f755b4caa2d", - "changeLog": [ - ".NET 10 yükseltildi.", - "Abp Framework 10 yükseltildi.", - "Sql Query Manager problemleri giderildi.", - "AuditLog problemleri giderildi.", - "Tenan yapısını uygunluğu için düzenlemeler yapıldı." - ] - }, - { - "version": "1.1.01", - "buildDate": "2026-05-24", - "commit": "6262baa6f12d695a25d83304af985092715d439a", - "changeLog": [ - "Workflow tanımlaması yapılabilir.", - "Dark mod için uygulama güncellemesi", - "Form Field kısmında düzenleme", - "Hangfire Recurring Job düzenlemesi." - ] - }, - { - "version": "1.0.10", - "buildDate": "2026-05-11", - "commit": "414006204e324018be597a598d3dd102868c5cca", - "changeLog": [ - "Backup dosyalarını son 5 günün kalması", - "Grid için Fit Columns özelliği eklendi.", - "Notification Desktop, UiActivity, UiToast özelliği eklendi", - "Versiyon güncellemeleri için \"System Updating\" mesajı" - ] - }, - { - "version": "1.0.9", - "buildDate": "2026-05-09", - "commit": "e9d8f5ebac35fc7b21f08139dfe175ed548c14b4", - "changeLog": [ - "Video Rooms düzenlemesi", - "Genel Liste düzenlemeleri" - ] - }, - { - "version": "1.0.8", - "buildDate": "2026-05-07", - "commit": "6fa266f23ed4f67384b7bef0e4030420cb788af5", - "changeLog": [ - "Intranet ve Sosyal Duvar", - "Listelerde ImageUploadandViewer", - "Survey Widget", - "Event, Category ve Type" - ] - }, - { - "version": "1.0.7", - "buildDate": "2026-05-04", - "commit": "4444fce93b4f62ad6698099fefe7da1614d2859c", - "changeLog": [ - "Departman tanımlama", - "Job Position tanımlama", - "Wizard ve SQl Data güncellemeleri", - "Organizasyon Şemanı eklendi" - ] - }, - { - "version": "1.0.6", - "buildDate": "2026-05-03", - "commit": "b2dfb04879a93709b04f8f0fc3bd4f5af1f7c01b", - "changeLog": [ - "SalesOrder ve SalesOrderItem listFormu tanımlandı.", - "Sql Backup dosyalarını File Manager içerisinde görüntülenebiliyor.", - "Wizard File Manager komponenti oluşturuldu.", - "Wizard komponentini Edit özelliği eklendi." - ] - }, - { - "version": "1.0.5", - "buildDate": "2026-04-28", - "commit": "b9cc68ff41d675fefadab79f5ea2c5e7f376708a", - "changeLog": [ - "AllowAdding özelliği eklendi.", - "Kulllanıcı bazında Workhour özelliği eklendi.", - "ConcurrentUser sınırlaması getirildi.", - "Uygulamadan kullanıcıyı kickleme özelliği getirildi.", - "Uygulama açıkken DbMigrator çağrısı yapıldı." - ] - }, - { - "version": "1.0.4", - "buildDate": "2026-03-30", - "commit": "e9ce256c0706de408e08a462bee9e4a72653f2c7", - "changeLog": [ - "Role Yetkilerini kopyalama", - "Place Holder güncellemesi", - "Kayıt kopyalama özelliği" - ] - }, - { - "version": "1.0.3", - "buildDate": "2026-03-28", - "commit": "130d35c37703935e34948419f2ee900cc1b6b28c", - "changeLog": [ - "Report Template View And Design" - ] - }, - { - "version": "1.0.2", - "buildDate": "2026-03-21", - "commit": "196b0dc24b7a815586166679b58ab4f9334bd40e", - "changeLog": [ - "Artificial Intelligence Chat Bot güncellemeleri" - ] - }, - { - "version": "1.0.1", - "buildDate": "2026-03-18", - "commit": "ac7784c030825158641e9042c50dede2067c0486", - "changeLog": [ - "Public Site Designer", - "Menu Groups", - "Sql Query Manager", - "Listform Wizard" - ] - } - ] -} \ No newline at end of file diff --git a/ui/scripts/generate-version.js b/ui/scripts/generate-version.js deleted file mode 100644 index b616d9c8..00000000 --- a/ui/scripts/generate-version.js +++ /dev/null @@ -1,117 +0,0 @@ -// scripts/generate-version.js -// -// public/version.json üretir. Bu dosya uygulamanın tek sürüm kaynağıdır: -// - `buildId` : HER deploy'da değişir (commit + build zamanı). Uygulama açık -// sekmede ve açılışta yeni deploy'u bununla anlar; git tag -// atılmamış olsa bile güncelleme algılanır. -// - `version` : Kullanıcıya gösterilen sürüm (en güncel release). -// - `releases` : Changelog kaynağı. Git tag'lerinden okunur; HEAD tag'li -// değilse son tag'den bu yana atılan commit'ler "yayınlanmamış" -// sürüm olarak en üste eklenir, böylece her deploy'un notu olur. -import fs from 'fs' -import path from 'path' -import { fileURLToPath } from 'url' -import { execSync } from 'child_process' - -function safeExec(cmd) { - try { - return execSync(cmd, { stdio: ['pipe', 'pipe', 'ignore'], shell: true }).toString().trim() - } catch { - return null - } -} - -const scriptDir = path.dirname(fileURLToPath(import.meta.url)) -const uiRoot = path.resolve(scriptDir, '..') -const outputFile = path.join(uiRoot, 'public', 'version.json') - -const pkg = JSON.parse(fs.readFileSync(path.join(uiRoot, 'package.json'), 'utf8')) -const packageVersion = pkg.version || '0.0.0' - -const buildTime = new Date().toISOString() -const buildDate = buildTime.slice(0, 10) - -const hasGit = !!safeExec('git rev-parse --is-inside-work-tree') - -/** - * Docker imajı içinde `.git` yoktur; sürüm bilgisi host'ta (deploy script'i ile) - * üretilip `public/version.json` olarak kopyalanır. Bu durumda dosyayı yeniden - * üretmek gerçek changelog'u ve `buildId`'yi silip yerine "yerel derleme" - * yazardı; bu yüzden mevcut dosya korunur. - */ -if (!hasGit && fs.existsSync(outputFile)) { - const existing = JSON.parse(fs.readFileSync(outputFile, 'utf8')) - const version = existing.version ?? existing.releases?.[0]?.version ?? packageVersion - const buildId = existing.buildId ?? existing.commit ?? `${packageVersion}.${Date.now().toString(36)}` - - if (!existing.buildId || !existing.version) { - existing.buildId = buildId - existing.version = version - fs.writeFileSync(outputFile, JSON.stringify(existing, null, 2)) - } - console.log(`> mevcut version.json korundu: v${version} (${buildId})`) - process.exit(0) -} -const shortCommit = process.env.GIT_COMMIT || (hasGit ? safeExec('git rev-parse --short HEAD') : null) -const commit = shortCommit || 'local' - -/** Tag mesajını changelog satırlarına çevirir. */ -const toChangeLog = (raw) => - (raw ?? '') - .split('\n') - .map((line) => line.replace(/^\s*[-*]\s*/, '').trim()) - .filter(Boolean) - -let releases = [] - -if (hasGit) { - // Sıralama tag adına göre (creatordate değil): sunucuda sonradan atılmış bir - // tag listeyi başa geçirip yanlış sürüm göstermesin. - const rawTags = safeExec('git tag --list --sort=-v:refname') - const tags = rawTags ? rawTags.split('\n').filter(Boolean) : [] - - releases = tags - .map((tag) => { - const version = tag.replace(/^v/, '') - const date = safeExec(`git log -1 --format=%ad --date=short ${tag}`) - const commitId = safeExec(`git rev-list -n 1 ${tag}`) - const changeLog = toChangeLog(safeExec(`git tag -l --format="%(contents)" ${tag}`)) - - return { - version, - buildDate: date || buildDate, - commit: commitId, - changeLog: changeLog.length ? changeLog : ['Bu sürüm için not girilmemiş'], - } - }) - - // Changelog yalnızca tag'lerden oluşur. Tag'siz commit'ler için sahte bir - // sürüm üretilmez; sürüm notlarını tag mesajı belirler. Deploy algılaması - // sürüm numarasına değil, her derlemede değişen `buildId`'ye bakar. -} - -if (releases.length === 0) { - releases = [ - { - version: packageVersion, - buildDate, - commit, - changeLog: ['Yerel geliştirme derlemesi', 'Git bilgisi bulunamadı, package.json sürümü kullanıldı'], - }, - ] -} - -const versionInfo = { - // Her derlemede değişir; uygulamanın deploy algılaması buna bakar. - buildId: `${commit}.${Date.now().toString(36)}`, - buildTime, - buildDate, - commit, - packageVersion, - version: releases[0].version, - releases, -} - -fs.mkdirSync(path.dirname(outputFile), { recursive: true }) -fs.writeFileSync(outputFile, JSON.stringify(versionInfo, null, 2)) -console.log(`> version.json: v${versionInfo.version} (${versionInfo.buildId})`) diff --git a/ui/scripts/generate-version.sh b/ui/scripts/generate-version.sh deleted file mode 100644 index d7d42ac2..00000000 --- a/ui/scripts/generate-version.sh +++ /dev/null @@ -1,50 +0,0 @@ -#!/bin/bash -set -e - -# public/version.json üretir (deploy öncesi, HOST üzerinde). -# -# Asıl uygulama scripts/generate-version.js dosyasıdır; node varsa o çalışır. -# Aşağıdaki bash karşılığı yalnızca node bulunmayan sunucular içindir ve -# js sürümüyle aynı çıktıyı üretmelidir. - -cd "$(dirname "$0")/.." - -echo "> version.json oluşturuluyor..." - -if command -v node >/dev/null 2>&1; then - node scripts/generate-version.js - exit 0 -fi - -# origin'de olmayan yerel tag'ler sürüm numarasını bozar; temizle. -git fetch --prune --prune-tags --tags origin >/dev/null 2>&1 || true - -# Sürüm sırası tag adına göre (creatordate değil); 1.1.10 > 1.1.09. -VERSIONS=$(git tag --sort=-v:refname) -ROOT_COMMIT=$(git rev-parse --short HEAD) -BUILD_TIME=$(date -u +%Y-%m-%dT%H:%M:%SZ) -BUILD_DATE=$(date -u +%Y-%m-%d) -# Her deploy'da değişir; uygulamanın yeni sürüm algılaması buna bakar. -BUILD_ID="${ROOT_COMMIT}.$(date -u +%s)" - -RELEASES="" -FIRST=true -for TAG in $VERSIONS; do - VER=$(echo "$TAG" | sed 's/^v//') - DATE=$(git log -1 --format=%ad --date=short "$TAG") - COMMIT=$(git rev-list -n 1 "$TAG") - MESSAGE=$(git tag -l --format="%(contents)" "$TAG" | grep -v '^$' | sed 's/^[[:space:]]*-[[:space:]]*//' | jq -R . | jq -s .) - - if [ "$FIRST" = true ]; then FIRST=false; else RELEASES+=","; fi - RELEASES+="{\"version\":\"$VER\",\"buildDate\":\"$DATE\",\"commit\":\"$COMMIT\",\"changeLog\":$MESSAGE}" -done - -# Sürüm = en son tag. Tag'siz commit'ler için sahte sürüm üretilmez; deploy -# algılaması sürüm numarasına değil, her derlemede değişen buildId'ye bakar. -LAST_TAG=$(echo "$VERSIONS" | head -n1 | sed 's/^v//') -VERSION=${LAST_TAG:-$(jq -r .version package.json)} - -printf '{"buildId":"%s","buildTime":"%s","buildDate":"%s","commit":"%s","version":"%s","releases":[%s]}' \ - "$BUILD_ID" "$BUILD_TIME" "$BUILD_DATE" "$ROOT_COMMIT" "$VERSION" "$RELEASES" | jq . > public/version.json - -echo "> version.json: v${VERSION} (${BUILD_ID})" diff --git a/ui/src/components/template/Footer.tsx b/ui/src/components/template/Footer.tsx index 5ab02fb9..3c2a1462 100644 --- a/ui/src/components/template/Footer.tsx +++ b/ui/src/components/template/Footer.tsx @@ -34,7 +34,7 @@ const FooterContent = () => { {apiConfig && ( API: - {apiConfig["environment"].toString()}:{apiConfig["version"].toString()} + {apiConfig["environment"].toString()}:{apiConfig["apiVersion"].toString()} )} diff --git a/ui/src/components/template/Theme.tsx b/ui/src/components/template/Theme.tsx index abace5ea..c8ae2dd5 100644 --- a/ui/src/components/template/Theme.tsx +++ b/ui/src/components/template/Theme.tsx @@ -10,7 +10,7 @@ import { Helmet } from 'react-helmet' import { useNavigate, useLocation } from 'react-router-dom' import { getSetupStatus } from '@/services/setup.service' import { ROUTES_ENUM } from '@/routes/route.constant' -import { getRunningVersion } from '@/views/version/swRegistration' +import type { VersionInfo } from '@/views/version/swRegistration' let didInit = false @@ -74,12 +74,15 @@ const Theme = (props: CommonProps) => { useDarkMode() useTabFocus() - // Çalışan bundle'ın sürümü footer'da gösterilir; derleme anında gömülüdür, - // ağ isteği gerektirmez. + // Sürüm numarası backend'den (application-configuration) gelir; footer bunu + // gösterir. const setUiVersion = useStoreActions((actions) => actions.locale.setUiVersion) + const appVersion = useStoreState( + (state) => state.abpConfig.config?.extraProperties?.['changeLogs'], + ) as VersionInfo | undefined useEffect(() => { - setUiVersion(getRunningVersion()) - }, [setUiVersion]) + setUiVersion(appVersion?.uiVersion) + }, [appVersion, setUiVersion]) type ConfigProviderValue = NonNullable['value']> diff --git a/ui/src/services/releaseNote.service.ts b/ui/src/services/releaseNote.service.ts new file mode 100644 index 00000000..5ad4d1d6 --- /dev/null +++ b/ui/src/services/releaseNote.service.ts @@ -0,0 +1,26 @@ +import apiService from '@/services/api.service' + +/** `Sas_H_ReleaseNote` kaydı; git tag'lerinden tohumlanır. */ +export interface ReleaseNoteDto { + id: string + version: string + releaseDate: string + commit?: string + changeLog: string[] +} + +class ReleaseNoteService { + /** + * ChangeLog ekranının kaynağı. Uygulamanın çalışan sürüm numarası + * application-configuration ile geldiği için burada yalnızca tam liste alınır. + */ + async getList(): Promise { + const response = await apiService.fetchData({ + url: '/api/app/release-note', + method: 'GET', + }) + return response.data + } +} + +export const releaseNoteService = new ReleaseNoteService() diff --git a/ui/src/store/abpConfig.model.ts b/ui/src/store/abpConfig.model.ts index cbd794cb..ccbc8def 100644 --- a/ui/src/store/abpConfig.model.ts +++ b/ui/src/store/abpConfig.model.ts @@ -10,6 +10,7 @@ import { ApplicationConfigurationDto, ApplicationLocalizationRequestDto, } from '../proxy/config/models' +import { setAppVersionInfo } from '../views/version/swRegistration' export type Texts = Record> @@ -66,6 +67,10 @@ export const abpConfigModel: AbpConfigModel = { actions.setConfig(result.data) + // Sürüm ve sürüm notları backend'den bu yanıtla gelir; güncelleme motoru + // ayrı bir istek atmaz. + setAppVersionInfo(result.data.extraProperties?.['changeLogs']) + //Eğer login değilse if (result.data.currentUser.isAuthenticated) { await actions.getMenu() diff --git a/ui/src/sw.ts b/ui/src/sw.ts index d26d874c..be887671 100644 --- a/ui/src/sw.ts +++ b/ui/src/sw.ts @@ -55,7 +55,6 @@ const ASSET_CACHE = 'sozsoft-assets-v1' const THEME_CACHE = 'sozsoft-themes-v1' const FONT_CACHE = 'sozsoft-fonts-v1' const IMAGE_CACHE = 'sozsoft-images-v1' -const VERSION_CACHE = 'sozsoft-version-v1' const KNOWN_CACHES = [ SHELL_CACHE, @@ -63,7 +62,6 @@ const KNOWN_CACHES = [ THEME_CACHE, FONT_CACHE, IMAGE_CACHE, - VERSION_CACHE, ] /** @@ -577,9 +575,6 @@ self.addEventListener('message', (event) => { event.waitUntil(ensureWarmup()) return } - if (type === 'PING') { - event.source?.postMessage({ type: 'PONG', shellSize: MANIFEST.length }) - } }) /* ------------------------------------------------------------------ */ @@ -623,21 +618,6 @@ async function staleWhileRevalidate(request: Request, cacheName: string, limit: return cached ?? (await network) ?? Response.error() } -async function networkFirst(request: Request, cacheName: string, timeoutMs: number) { - const cache = await caches.open(cacheName) - try { - const controller = new AbortController() - const timer = setTimeout(() => controller.abort(), timeoutMs) - const response = await fetch(request, { signal: controller.signal }) - clearTimeout(timer) - if (response.ok) await cache.put(request, response.clone()) - return response - } catch { - const cached = await cache.match(request) - return cached ?? Response.error() - } -} - /** Kabuktaki index.html'i arka planda tazeler (deploy sonrası kendini onarır). */ async function refreshIndex(cache: Cache, key: string, url: string) { try { @@ -681,12 +661,6 @@ self.addEventListener('fetch', (event) => { return } - // Sürüm bilgisi asla eski kalmamalı. - if (url.pathname.endsWith('/version.json')) { - event.respondWith(networkFirst(request, VERSION_CACHE, 3000)) - return - } - const shellKey = shellKeys.get(url.href.split('?')[0]) if (shellKey) { event.respondWith( diff --git a/ui/src/views/version/AppUpdateDialog.tsx b/ui/src/views/version/AppUpdateDialog.tsx index 18cb1a88..e71602e6 100644 --- a/ui/src/views/version/AppUpdateDialog.tsx +++ b/ui/src/views/version/AppUpdateDialog.tsx @@ -41,8 +41,6 @@ const titleFor = (state: AppUpdateState) => { return 'Yeni sürüm hazırlanıyor' case 'activating': return 'Uygulama yeniden başlatılıyor' - case 'reload-required': - return 'Yeni sürüm yayınlandı' case 'error': return 'Güncelleme tamamlanamadı' default: @@ -65,8 +63,6 @@ const descriptionFor = (state: AppUpdateState) => { return 'İndirme tamamlandı, yeni sürüme geçiliyor.' case 'activating': return 'Sayfa otomatik olarak yenilenecek.' - case 'reload-required': - return `Yeni sürüme otomatik geçiliyor${countdownText(state)}.` case 'error': return `${state.message ?? 'Geçiş tamamlanamadı.'}${countdownText(state)}` default: @@ -133,7 +129,6 @@ const AppUpdateDialog = () => { const busy = state.status === 'downloading' || state.status === 'ready' const doneCount = state.files.filter((file) => file.status === 'done').length - const upgrading = !!state.version && state.version !== state.currentVersion const ordered = state.files.slice().sort(byActivity) return ( @@ -159,17 +154,8 @@ const AppUpdateDialog = () => {
- {upgrading ? 'Mevcut' : 'Sürüm'}: v{state.currentVersion} + Sürüm: v{state.currentVersion} - {/* İlk kurulumda geçilecek bir sürüm yok; aynı numarayı iki kez göstermeyelim. */} - {upgrading && ( - <> - - - Yeni: v{state.version} - - - )}
{(busy || state.files.length > 0) && ( @@ -213,7 +199,7 @@ const AppUpdateDialog = () => { {state.changeLog.length > 0 && (

- v{state.version} ile gelen yenilikler + v{state.currentVersion} ile gelen yenilikler

    {state.changeLog.map((line, index) => ( @@ -242,14 +228,13 @@ const AppUpdateDialog = () => { {state.status === 'completed' ? 'Tamam' : 'Arka planda devam et'} ) : ( - (state.status === 'reload-required' || state.status === 'error') && ( + state.status === 'error' && ( ) )} diff --git a/ui/src/views/version/ChangeLog.tsx b/ui/src/views/version/ChangeLog.tsx index 2c48b4c8..811c6803 100644 --- a/ui/src/views/version/ChangeLog.tsx +++ b/ui/src/views/version/ChangeLog.tsx @@ -6,13 +6,8 @@ import { useLocalization } from '@/utils/hooks/useLocalization' import React, { useEffect, useState } from 'react' import { Helmet } from 'react-helmet' import { FaRegCheckCircle, FaSyncAlt, FaTag } from 'react-icons/fa' -import { checkForAppUpdate, clearPendingChangelog, fetchVersionInfo } from './swRegistration' - -type Release = { - version: string - buildDate: string - changeLog: string[] -} +import { releaseNoteService, type ReleaseNoteDto } from '@/services/releaseNote.service' +import { checkForAppUpdate, clearPendingChangelog } from './swRegistration' // User dropdown reuses the exact update-check flow owned by this component. // eslint-disable-next-line react-refresh/only-export-components @@ -111,16 +106,21 @@ const Log = ({ const Changelog = () => { const { translate } = useLocalization() - const [releases, setReleases] = useState([]) const { isCheckingUpdate, checkForUpdate } = useManualUpdateCheck() + // Kaynak `ReleaseNote` entity'sidir; liste yalnızca bu ekran açıldığında + // istenir (application-configuration sadece çalışan sürümü taşır). + const [releases, setReleases] = useState([]) + useEffect(() => { // Bekleyen changelog bayrağı burada tüketilir; kullanıcı değişiklikleri // gördüğü için sonraki girişlerde tekrar yönlendirilmez. clearPendingChangelog() - // Açılışta zaten alınan sürüm bilgisi paylaşılır (ek istek atılmaz). - void fetchVersionInfo().then((info) => setReleases((info?.releases as Release[]) ?? [])) + void releaseNoteService + .getList() + .then(setReleases) + .catch((error: unknown) => console.warn('Sürüm notları alınamadı.', error)) }, []) return ( @@ -150,8 +150,8 @@ const Changelog = () => {
{releases.map((rel) => ( - - {rel.changeLog?.length > 0 && ( + + {!!rel.changeLog?.length && (
    {rel.changeLog.map((item, i) => (
  • diff --git a/ui/src/views/version/swRegistration.ts b/ui/src/views/version/swRegistration.ts index 88434e37..31e74278 100644 --- a/ui/src/views/version/swRegistration.ts +++ b/ui/src/views/version/swRegistration.ts @@ -5,23 +5,27 @@ import './installPrompt' /** * Sürüm / güncelleme motoru * ------------------------------------------------------------------ - * Tek sürüm kaynağı `public/version.json` (build'de `scripts/generate-version.js` - * üretir). Çalışan bundle'ın `buildId`'si derleme anında gömülür; sunucudaki - * `version.json` ile karşılaştırıldığı için git tag'i atılmasa bile YENİ DEPLOY - * kesin olarak anlaşılır. + * Sürüm numarası ve sürüm notları backend'den gelir: application-configuration + * yanıtındaki `extraProperties.changeLogs`. Store, config'i her aldığında + * `setAppVersionInfo()` ile bu modülü besler; ayrı bir sürüm isteği yoktur. + * + * Deploy algılaması service worker'ın kendi güncelleme kontrolüne dayanır: + * herhangi bir chunk değiştiğinde entry chunk'ın hash'i, dolayısıyla index.html + * ve `sw.js` içindeki precache manifest'i değişir; `registration.update()` yeni + * worker'ı görür. Böylece ayrı bir sürüm dosyası yoklamaya gerek kalmaz. * * Akış: - * 1. Açılışta tek bir `version.json` isteği (sonuç önbelleklenir, diğer - * ekranlar `fetchVersionInfo()` ile aynı sonucu kullanır). - * 2. `buildId` farklıysa service worker güncellemesi tetiklenir. - * 3. Service worker (`src/sw.ts`) indirdiği her dosyayı isim/boyut/yüzde - * olarak yayınlar; bu modül state'e çevirir, pencere (lazy) gösterir. - * 4. Kurulum bitince SKIP_WAITING → `controllerchange` → reload. - * 5. Yeni build ilk kez çalıştığında changelog bayrağı yazılır; login sonrası - * kullanıcı ChangeLog ekranına yönlendirilir. + * 1. Service worker kaydı + periyodik/olay tabanlı `update()` kontrolü. + * 2. Yeni worker bulunursa kurulum başlar; `src/sw.ts` indirdiği her dosyayı + * isim/boyut/yüzde olarak yayınlar, bu modül state'e çevirir. + * 3. Kurulum bitince SKIP_WAITING → `controllerchange` → reload. + * 4. Backend'den gelen sürüm son görülenden farklıysa changelog bayrağı + * yazılır; login sonrası kullanıcı ChangeLog ekranına yönlendirilir. + * Tetikleyici sürümdür, bundle değil: yalnızca yeni bir sürüm notu (git + * tag) eklendiğinde de kullanıcı değişiklikleri görür. * - * Bu dosya entry chunk'ındadır: React/UI bağımlılığı yoktur, pencere ve - * bildirim bileşenleri yalnızca gerçekten güncelleme varken lazy yüklenir. + * Bu dosya entry chunk'ındadır: React/UI/store bağımlılığı yoktur, pencere + * yalnızca gerçekten indirme varken lazy yüklenir. */ /* ------------------------------------------------------------------ */ @@ -58,16 +62,13 @@ export type AppUpdateStatus = /** Tüm bileşenler indi ve geçiş gerekmiyor (ilk kurulum). */ | 'completed' | 'activating' - | 'reload-required' | 'error' export type AppUpdateState = { status: AppUpdateStatus /** Pencere açık mı. */ visible: boolean - /** Sunucudaki (yeni) sürüm. */ - version?: string - /** Çalışan bundle'ın sürümü. */ + /** Uygulamanın çalışan sürümü (backend'den gelir). */ currentVersion: string changeLog: string[] files: UpdateFile[] @@ -89,50 +90,34 @@ export type AppUpdateState = { dismissible: boolean } -export type Release = { - version: string - buildDate?: string - commit?: string - changeLog?: string[] -} - +/** + * `extraProperties.changeLogs` yükü: yalnızca ÇALIŞAN sürüm. Tam sürüm notu + * listesi ChangeLog ekranının kendi endpoint'inden gelir, her config isteğinde + * taşınmaz. + */ export type VersionInfo = { - buildId: string - version: string - buildTime?: string + /** Uygulamanın (UI) çalışan sürümü. */ + uiVersion: string buildDate?: string - commit?: string - releases: Release[] + /** Son sürümün notları. */ + tags?: string[] } /* ------------------------------------------------------------------ */ /* Sabitler */ /* ------------------------------------------------------------------ */ -declare const __APP_BUILD_ID__: string -declare const __APP_VERSION__: string - -const RUNNING_BUILD_ID = typeof __APP_BUILD_ID__ === 'string' ? __APP_BUILD_ID__ : 'dev' -const RUNNING_VERSION = typeof __APP_VERSION__ === 'string' ? __APP_VERSION__ : '0.0.0' - /** Sekme açık kalırsa periyodik yoklama. */ const VERSION_POLL_INTERVAL = 30 * 60 * 1000 -/** Aynı version.json sonucunun paylaşıldığı süre (açılışta tek istek). */ -const VERSION_CACHE_TTL = 60 * 1000 -/** İki deploy yoklaması arasındaki en kısa süre (sekmeye dönüş, online vb.). */ -const MIN_VERSION_CHECK_GAP = 5 * 60 * 1000 const MIN_CHECK_GAP = 30 * 1000 const ACTIVATION_RETRY_DELAY = 8_000 const ACTIVATION_TIMEOUT = 45_000 const REGISTRATION_TIMEOUT = 10_000 const UPDATE_CALL_TIMEOUT = 20_000 -/** Yenileme gerektiren deploy'da otomatik geçişe kalan süre. */ -const AUTO_RELOAD_SECONDS = 5 /** Kurulum/aktivasyon başarısız olduğunda otomatik yeniden deneme süresi. */ const RETRY_DELAY_SECONDS = 15 -const LAST_BUILD_KEY = 'sozsoft:last-build-id' -const AUTO_RELOAD_GUARD_KEY = 'sozsoft:auto-reload-build' +const LAST_VERSION_KEY = 'sozsoft:last-version' const PENDING_CHANGELOG_KEY = 'sozsoft:pending-changelog' const CHUNK_RELOAD_GUARD_KEY = 'sozsoft:chunk-reload' @@ -170,7 +155,8 @@ const writeStorage = (key: string, value: string | undefined, session = false) = let state: AppUpdateState = { status: 'idle', visible: false, - currentVersion: RUNNING_VERSION, + // Backend'den (`setAppVersionInfo`) gelene kadar boş kalır. + currentVersion: '', changeLog: [], files: [], loadedBytes: 0, @@ -254,90 +240,43 @@ function recalculate( /* Sürüm bilgisi */ /* ------------------------------------------------------------------ */ -let versionInfoPromise: Promise | undefined -let inFlightVersionRequest: Promise | undefined -let lastVersionFetchAt = 0 -let latestInfo: VersionInfo | undefined - -const normalize = (raw: unknown): VersionInfo | undefined => { - const data = raw as Partial | undefined - if (!data) return undefined - const releases = Array.isArray(data.releases) ? data.releases : [] - const version = data.version ?? releases[0]?.version - if (!version) return undefined - return { - buildId: String(data.buildId ?? data.commit ?? version), - version, - buildTime: data.buildTime, - buildDate: data.buildDate ?? releases[0]?.buildDate, - commit: data.commit, - releases, - } -} - /** - * `version.json` — tek sürüm kaynağı (deploy yoklaması + changelog). + * Sürüm bilgisini application-configuration yanıtından alır. * - * Uçuşta bir istek varsa ikinci bir ağ isteği atılmaz; `force` verilmediğinde - * kısa süre içinde (TTL) alınan sonuç paylaşılır. Böylece açılışta motor, - * pencere ve ChangeLog ekranı tek isteği kullanır. + * Store, config'i her yüklediğinde bunu çağırır; ayrı bir sürüm isteği yoktur. + * Yük şekli: `extraProperties.changeLogs = { uiVersion, buildDate, tags[] }`. */ -export const fetchVersionInfo = (force = false): Promise => { - if (inFlightVersionRequest) return inFlightVersionRequest - if (!force && versionInfoPromise && Date.now() - lastVersionFetchAt < VERSION_CACHE_TTL) { - return versionInfoPromise - } +export const setAppVersionInfo = (raw: unknown) => { + const data = raw as Partial | undefined + if (!data?.uiVersion) return - inFlightVersionRequest = fetch(`/version.json?ts=${Date.now()}`, { cache: 'no-store' }) - .then((response) => { - if (!response.ok) throw new Error(`version.json isteği ${response.status} döndü`) - return response.json() - }) - .then((data) => { - const info = normalize(data) - if (info) { - latestInfo = info - lastVersionFetchAt = Date.now() - } - return info - }) - .catch((error: unknown) => { - console.warn('Sürüm bilgisi okunamadı.', error) - versionInfoPromise = undefined - return undefined - }) - .finally(() => { - inFlightVersionRequest = undefined - }) + setState({ + currentVersion: data.uiVersion, + changeLog: Array.isArray(data.tags) ? data.tags.filter(Boolean) : [], + }) - versionInfoPromise = inFlightVersionRequest - return inFlightVersionRequest + markVersionSeen(data.uiVersion) } -export const getRunningVersion = () => RUNNING_VERSION -export const getLatestVersionInfo = () => latestInfo - /** Login sonrası changelog yönlendirmesi için bayrak. */ export const hasPendingChangelog = () => !!readStorage(PENDING_CHANGELOG_KEY) -export const consumePendingChangelog = (): string | undefined => { - const value = readStorage(PENDING_CHANGELOG_KEY) - if (value) writeStorage(PENDING_CHANGELOG_KEY, undefined) - return value -} - export const clearPendingChangelog = () => writeStorage(PENDING_CHANGELOG_KEY, undefined) /** - * Yeni bir build ilk kez çalıştığında (güncelleme sonrası ya da kullanıcı - * uygulamaya deploy'dan sonra ilk kez girdiğinde) changelog bayrağını yazar. + * Sürüm numarası son görülenden farklıysa changelog bayrağını yazar; kullanıcı + * bir sonraki girişinde ChangeLog ekranına yönlendirilir. + * + * Tetikleyici bilerek BUNDLE DEĞİL, backend'den gelen sürümdür: yalnızca yeni + * bir sürüm notu (git tag) eklendiğinde de — hiçbir bileşen değişmemiş olsa + * bile — kullanıcı değişiklikleri görür. İlk açılışta (kayıt yokken) bayrak + * yazılmaz, yalnızca mevcut sürüm not edilir. */ -function markRunningBuildSeen(version: string) { - const lastSeen = readStorage(LAST_BUILD_KEY) - if (lastSeen && lastSeen !== RUNNING_BUILD_ID) { - writeStorage(PENDING_CHANGELOG_KEY, version) - } - if (lastSeen !== RUNNING_BUILD_ID) writeStorage(LAST_BUILD_KEY, RUNNING_BUILD_ID) +function markVersionSeen(version: string) { + const lastSeen = readStorage(LAST_VERSION_KEY) + if (lastSeen === version) return + if (lastSeen) writeStorage(PENDING_CHANGELOG_KEY, version) + writeStorage(LAST_VERSION_KEY, version) } /* ------------------------------------------------------------------ */ @@ -518,7 +457,7 @@ function activateWaitingWorker() { /* ------------------------------------------------------------------ */ type SwFile = { - index?: number + index: number url: string name: string loaded: number @@ -528,7 +467,7 @@ type SwFile = { type SwProgressMessage = { type?: string - /** Tam liste (START / DONE). */ + /** Tam liste (START / STATE / DONE). */ files?: SwFile[] /** Yalnızca değişen dosyalar (PROGRESS). */ updates?: SwFile[] @@ -540,8 +479,8 @@ type SwProgressMessage = { } const toFiles = (files: SwFile[] | undefined): UpdateFile[] => - (files ?? []).map((file, position) => ({ - index: file.index ?? position, + (files ?? []).map((file) => ({ + index: file.index, url: file.url, name: file.name, loaded: file.loaded ?? 0, @@ -625,8 +564,7 @@ function handleWorkerMessage(event: MessageEvent) { case 'SW_PRECACHE_PROGRESS': // Durum isteği bir yarışa takılıp cevapsız kalırsa pencere burada açılır. if (!dialogMountRequested) void showUpdateDialog('downloading') - // `files`: yalnızca eski service worker'lar (tam liste) için. - recalculate(mergeFiles(toFiles(data.updates ?? data.files)), totals, { + recalculate(mergeFiles(toFiles(data.updates)), totals, { status: 'downloading', visible: true, dismissible: !transitions, @@ -696,64 +634,12 @@ async function showUpdateDialog(status: AppUpdateStatus) { /* Sürüm kontrolü */ /* ------------------------------------------------------------------ */ -function applyVersionInfo(info: VersionInfo) { - const release = info.releases[0] - setState({ - version: info.version, - changeLog: release?.changeLog?.filter(Boolean) ?? [], - }) -} - /** - * Sunucudaki build çalışan build'den farklı mı? - * - * Dev'de karşılaştırma yapılmaz: `__APP_BUILD_ID__` vite config yüklenirken - * gömüldüğü için `version.json` yeniden üretildiğinde sunucu yeniden - * başlatılana kadar kalıcı olarak farklı görünür ve sonsuz yenileme oluşur. + * Deploy'da service worker dosyası mutlaka değişir: herhangi bir chunk'ın + * hash'i değiştiğinde entry chunk, dolayısıyla index.html ve `sw.js` içindeki + * precache manifest'i de değişir. Bu yüzden ayrı bir sürüm dosyası yoklamaya + * gerek yok; `registration.update()` yeni deploy'u tek başına yakalar. */ -const isNewDeploy = (info: VersionInfo | undefined) => - !!info && import.meta.env.PROD && RUNNING_BUILD_ID !== 'dev' && info.buildId !== RUNNING_BUILD_ID - -/** Deploy yoklaması: `version.json` okunur (sürüm + changelog aynı dosyada). */ -async function checkVersionEndpoint(force = false) { - const now = Date.now() - if (now - lastVersionCheckAt < (force ? MIN_CHECK_GAP : MIN_VERSION_CHECK_GAP)) return false - lastVersionCheckAt = now - - const info = await fetchVersionInfo(force) - if (!info) return false - applyVersionInfo(info) - - if (!isNewDeploy(info)) return false - - const requireReload = () => { - void showUpdateDialog('reload-required') - - // Yenileme sonrası hâlâ aynı build çalışıyorsa (ör. sunucu eski dosyayı - // servis etmeye devam ediyorsa) otomatik yenilemeyi tekrarlamak sonsuz - // döngü demektir; bu durumda karar kullanıcıya bırakılır. - if (readStorage(AUTO_RELOAD_GUARD_KEY, true) === info.buildId) { - setState({ - secondsLeft: undefined, - message: 'Yeni sürüme geçilemedi. Yenilemeyi tekrar deneyebilirsiniz.', - }) - return - } - writeStorage(AUTO_RELOAD_GUARD_KEY, info.buildId, true) - // Kullanıcı onayı beklenmez; kısa bir bilgilendirmeden sonra otomatik geçilir. - scheduleAutoAction(AUTO_RELOAD_SECONDS, () => void reloadWithFreshShell()) - } - - if (isServiceWorkerEnabled && 'serviceWorker' in navigator) { - await runUpdateCheck(true) - // Service worker aynı kabuk dosyalarını görüyorsa (ör. yalnızca lazy chunk - // değişmiş) kurulum tetiklenmez; bu durumda sayfa yenilemesi yeterlidir. - if (state.status === 'idle' || state.status === 'checking') requireReload() - } else { - requireReload() - } - return true -} async function runUpdateCheck(force = false) { if (!registration || activating || reloading) return @@ -871,17 +757,17 @@ function startWatchers() { // yalnızca sekme uzun süre açık ve önde kalırsa devreye girer. window.clearInterval(pollTimer) pollTimer = window.setInterval(() => { - if (document.visibilityState === 'visible') void checkVersionEndpoint() + if (document.visibilityState === 'visible') void runUpdateCheck() }, VERSION_POLL_INTERVAL) document.addEventListener('visibilitychange', () => { if (document.visibilityState !== 'visible') return - void checkVersionEndpoint() + void runUpdateCheck() }) window.addEventListener('online', () => { // Çevrimdışıyken beklemeye alınan güncelleme bağlantı gelince sürer. if (state.status === 'error') void retryUpdate() - else void checkVersionEndpoint() + else void runUpdateCheck() }) } @@ -926,20 +812,11 @@ export const registerServiceWorker = () => { started = true scope.__sozsoftUpdateEngineStarted = true - // Sürüm okuma ve deploy kontrolü service worker'dan bağımsızdır; dev'de de - // (SW kapalıyken) yeni deploy algılanır. - markRunningBuildSeen(RUNNING_VERSION) - - // Açılış performansı: sürüm isteği ve SW kaydı ilk boyamayı beklesin. - // Açılışta `force` verilmez; bildirim kancasıyla aynı istek paylaşılır. - const boot = () => { - void checkVersionEndpoint() - startWatchers() - } + // Açılış performansı: yoklama dinleyicileri ilk boyamayı beklesin. const idle = (window as unknown as { requestIdleCallback?: (cb: () => void, o?: object) => void }) .requestIdleCallback - if (idle) idle.call(window, boot, { timeout: 3_000 }) - else window.setTimeout(boot, 1_500) + if (idle) idle.call(window, startWatchers, { timeout: 3_000 }) + else window.setTimeout(startWatchers, 1_500) if (!('serviceWorker' in navigator)) return @@ -996,9 +873,6 @@ export const registerServiceWorker = () => { export const checkForAppUpdate = async (): Promise => { setState({ status: state.status === 'idle' ? 'checking' : state.status }) - const newDeploy = await checkVersionEndpoint(true) - if (newDeploy) return 'updating' - if (!('serviceWorker' in navigator)) return 'unsupported' if (!isServiceWorkerEnabled) return 'disabled' diff --git a/ui/src/vite-env.d.ts b/ui/src/vite-env.d.ts index 70d26e11..8e04ddac 100644 --- a/ui/src/vite-env.d.ts +++ b/ui/src/vite-env.d.ts @@ -4,7 +4,6 @@ interface ImportMetaEnv { readonly VITE_API_URL: string readonly VITE_CDN_URL: string - readonly VITE_REACT_APP_VERSION: string readonly VITE_AI_URL: string readonly VITE_USE_POLLING?: string /** Dev sunucusunda service worker'ı açar (varsayılan kapalı). */ diff --git a/ui/vite.config.ts b/ui/vite.config.ts index 6294df57..8c32da3a 100644 --- a/ui/vite.config.ts +++ b/ui/vite.config.ts @@ -1,7 +1,6 @@ import { defineConfig, loadEnv } from 'vite' import type { Plugin } from 'vite' import react from '@vitejs/plugin-react' -import fs from 'fs' import path from 'path' import { VitePWA } from 'vite-plugin-pwa' @@ -82,27 +81,8 @@ function warmupManifest(): Plugin { } } -/** - * `scripts/generate-version.js` build'den önce çalışır. Buradaki değerleri - * bundle'a gömüyoruz ki uygulama, çalışan kodun build kimliğini sunucudaki - * `version.json` ile birebir karşılaştırıp yeni deploy'u kesin olarak anlasın. - */ -function readBuildInfo() { - try { - const file = path.join(import.meta.dirname, 'public', 'version.json') - const data = JSON.parse(fs.readFileSync(file, 'utf8')) - return { - buildId: String(data.buildId ?? data.commit ?? 'dev'), - version: String(data.version ?? data.releases?.[0]?.version ?? '0.0.0'), - } - } catch { - return { buildId: 'dev', version: '0.0.0' } - } -} - export default defineConfig(({ mode }) => { const env = loadEnv(mode, process.cwd(), '') - const buildInfo = readBuildInfo() const usePolling = env.VITE_USE_POLLING === 'true' // Dev'de service worker varsayılan olarak kapalıdır; açık olduğunda HMR // sonrası eski chunk'lar cache'den servis edilip kafa karıştırıyor. @@ -292,10 +272,6 @@ export default defineConfig(({ mode }) => { define: { 'process.env': {}, - // Çalışan bundle'ın kimliği. İstemci bunu sunucudaki version.json ile - // karşılaştırarak yeni deploy'u kesin olarak (tag'e ihtiyaç duymadan) anlar. - __APP_BUILD_ID__: JSON.stringify(buildInfo.buildId), - __APP_VERSION__: JSON.stringify(buildInfo.version), }, } })