dotnet tool ve dotnet new Template'leri: Local Tool Manifest, dnx, Kendi Aracını ve Template'ini Paketlemek
Bir .NET aracını repoya bağlamanın yolu local tool manifest'tir: sürüm dotnet-tools.json'da commit'lenir ve CI dotnet tool restore ile başlar. bookstore-admin'i PackAsTool ile paketleyip şirket feed'inden Reporting'e kuruyor, dnx'in source mapping'e takılmasını, SDK 11'in NETSDK1235 uyarısını, bookstore-module template paketini ve #:include kullanan bir file-based script'i gerçek çıktılarla gösteriyoruz.
Sürüm notu: Bu yazıdaki kodlar .NET 10 (SDK 10.0.404) ile derlenip denenmiştir; Reporting örnekleri SDK 11.0.100, şirket içi feed BaGetter 1.6.5 ile çalıştırılmıştır.
Önce Tahmin Et
Emre, sitedeki serilerin ortak örnek uygulaması Bookstore'un (bir online kitapçı) gece çalışan bir rapor script'ine şu satırı ekledi ve kendi makinesinde denedi:
dotnet bookstore-admin info
Script onun makinesinde çalıştı, CI agent'ında şöyle bitti:
Could not execute because the specified command or file was not found.
Possible reasons for this include:
* You misspelled a built-in dotnet command.
* You intended to execute a .NET program, but dotnet-bookstore-admin does not exist.
* You intended to run a global tool, but a dotnet-prefixed executable with this name could not be found on the PATH.
Komut aynı, SDK aynı. Fark neredeydi? Emre aracı aylar önce global tool olarak kurmuştu; araç onun kullanıcı profilinde duruyordu, repoda değil. Bu yazıda .NET tool'larını repoya bağlamayı (local tool manifest), kurmadan çalıştırmayı (dnx), bookstore-admin'i bir tool paketine çevirmeyi ve yeni module'ler için bir dotnet new template'i yazmayı anlatıyoruz. Sonda da proje gerektirmeyen file-based app'lere bakıyoruz.
Global Tool, Local Tool ve Manifest
.NET tool, içinde bir console uygulaması taşıyan özel bir NuGet paketi. Üç yere kurulabilir: -g ile kullanıcı profiline (global), --tool-path ile seçtiğin bir klasöre ya da bir manifest dosyasına (local). Global tool o makinedeki herkes için tek sürümdür ve repoyla birlikte gelmez; Emre'nin sorunu da buydu. Local tool ise repodaki dotnet-tools.json'a yazılır, sürümü commit'lenir ve herkes aynı sürümü kullanır. Bookstore'un kökünde zaten bir manifest var, Entity Framework Core'un dotnet-ef aracı orada:
{
"version": 1,
"isRoot": true,
"tools": {
"dotnet-ef": {
"version": "10.0.15",
"commands": [
"dotnet-ef"
],
"rollForward": false
}
}
}
dotnet tool restore
Tool 'dotnet-ef' (version '10.0.15') was restored. Available commands: dotnet-ef
A newer version of tool 'dotnet-ef' is available (version '11.0.0'). Consider updating your manifest file.
Restore was successful.
dotnet tool restore, manifest'teki her aracı NuGet'in global packages klasörüne indirir; araç kurulmuş olmaz, yalnız paketi hazır olur. Sonrasında dotnet ef ya da dotnet tool run dotnet-ef manifest'teki sürümü çalıştırır. Yeni sürüm bildirimi SDK 10.0.404'te geldi; ama rollForward: false olduğu için manifest değişmeden yükseltme yapılmaz. Entity Framework Core 11 aracını .NET 10 uygulamasında kullanmak istemiyoruz, bildirimi bilerek görmezden geliyoruz.
isRoot: true, CLI'ın manifest ararken üst klasörlere çıkmasını durdurur. Manifest yoksa dotnet bookstore-admin gibi bir komut önce yerleşik komutlara, sonra PATH'teki dotnet- önekli dosyalara bakar; girişteki hata mesajı bu sırayı anlatıyor. CI için kural basit: pipeline dotnet tool restore ile başlar ve hiçbir script global tool varsaymaz.
bookstore-admin'i Tool Olarak Paketlemek
Raporlama ekibi kendi reposunda bookstore-admin'i kullanmak istiyor. Kaynak kodu kopyalamak yerine aracı paketleyip 4. yazıda kurduğumuz şirket içi feed'e koyduk. Proje dosyasına birkaç property yetiyor:
<PropertyGroup>
<OutputType>Exe</OutputType>
<AssemblyName>bookstore-admin</AssemblyName>
<!-- Trim, single-file ve AOT analyzer'ları yalnız publish'te değil her build'de çalışsın. -->
<IsAotCompatible>true</IsAotCompatible>
<!-- dotnet pack bir .NET tool paketi üretir; komut adı varsayılan olarak AssemblyName'dir. -->
<PackAsTool>true</PackAsTool>
<PackageId>Bookstore.Admin</PackageId>
<Version>1.0.0</Version>
<Description>Admin commands for Bookstore: info, ISBN check, price formatting.</Description>
</PropertyGroup>
dotnet pack tools/Bookstore.Admin
Bookstore.Admin -> C:\src\Bookstore\artifacts\publish\Bookstore.Admin\release\
Successfully created package 'C:\src\Bookstore\artifacts\package\release\Bookstore.Admin.1.0.0.nupkg'.
Çıktıdaki publish satırına dikkat: tool paketi aslında paketlenmiş bir publish klasörü. İçinde tools/net10.0/any/ altında dll'ler, appsettings.json ve SDK'nın ürettiği DotnetToolSettings.xml var:
<DotNetCliTool Version="1">
<Commands>
<Command Name="bookstore-admin" EntryPoint="bookstore-admin.dll" Runner="dotnet" />
</Commands>
</DotNetCliTool>
ToolCommandName'i yazmadık; komut adı AssemblyName'den geldi. Runner="dotnet" ve any klasörü aracın framework-dependent ve platformdan bağımsız olduğunu söylüyor, yani makinede .NET 10 runtime'ı olmalı. .NET 10 ile tool paketleri RID'e (Runtime Identifier; win-x64 gibi platform adı) özel, self-contained ya da Native AOT (Ahead-of-Time; publish sırasında makine koduna derleme) olarak da üretilebiliyor; bir önceki yazıdaki publish seçeneklerinin hepsi tool'a uygulanabiliyor. Bookstore'un araçları için buna ihtiyaç duymadık, denemedik.
Paketi feed'e push ettik ve Reporting reposunda bir manifest oluşturup aracı kurduk. Reporting'in global.json'u SDK 11.0.100'ü seçiyor, feed'in kullanıcı adı ve şifresi 4. yazıdaki gibi ortam değişkenlerinde:
cd samples/reporting
dotnet new tool-manifest
dotnet tool install Bookstore.Admin
The template "Dotnet local tool manifest file" was created successfully.
You can invoke the tool from this directory using the following commands: 'dotnet tool run bookstore-admin' or 'dotnet bookstore-admin'.
Tool 'bookstore.admin' (version '1.0.0') was successfully installed. Entry is added to the manifest file C:\src\Bookstore\samples\reporting\dotnet-tools.json.
--local yazmadık, çünkü dotnet tool install'ın varsayılanı local; -g vermediğin sürece aracı manifest'e ekler. Reporting'in nuget.config'indeki source mapping Bookstore.* paketlerini yalnız şirket feed'ine yönlendirdiği için araç nuget.org'da aranmadı bile.
dotnet bookstore-admin isbn 978-0-441-17271-9
$env:BOOKSTORE_ENVIRONMENT = "Staging"; dotnet tool run bookstore-admin info
valid: 9780441172719
Runtime: .NET 10.0.15
Environment: Staging
API: http://localhost:5080
API adresi paketin içindeki appsettings.json'dan geliyor. Kurulan bir tool'un klasörü NuGet cache'inde durur ve elle düzenlenmez; ortama göre değişen her ayar ortam değişkeninden ya da komut satırından gelmeli. bookstore-admin ortamı zaten ortam değişkeninden okuyor; API adresini de aynı yoldan okumak bir sonraki sürümün işi.
Eski bir projeden kalma bir alışkanlığı da denedik: paket metadata'sını elle yazılmış bir .nuspec dosyasıyla vermek. SDK 11 bunun için yeni bir uyarı getirdi; denemeyi repo kopyasında global.json'u 11.0.100'e çekerek yaptık:
dotnet pack tools/Bookstore.Admin -p:NuspecFile=bookstore-admin.nuspec
C:\Program Files\dotnet\sdk\11.0.100\Sdks\Microsoft.NET.Sdk\targets\Microsoft.NET.PackTool.targets(283,5): warning NETSDK1235: .NET Tools do not support using a custom .nuspec file, but the nuspec file 'bookstore-admin.nuspec' was provided. .NET Tools use a custom package layout and package identifier convention and must be customized through MSBuild logic only. Remove the NuspecFile property from this project to enable packing it as a .NET Tool. [C:\src\Bookstore\tools\Bookstore.Admin\Bookstore.Admin.csproj]
Successfully created package 'C:\src\Bookstore\artifacts\package\release\Bookstore.Admin.1.0.0.nupkg'.
Uyarıya rağmen paket oluştu. İçine baktığımızda tools/ klasörü yoktu; onun yerine Bookstore.Admin.csproj, Program.cs ve publish profile'ı vardı. Yani feed'e çalışmayan ve kaynak kodu taşıyan bir paket gidecekti. Daha da önemlisi, TreatWarningsAsErrors bu uyarıyı hataya çevirmedi: o property derleyici ve NuGet uyarılarını etkiliyor, SDK'nın MSBuild task'larından gelen NETSDK uyarılarını değil. Onlar için MSBuildTreatWarningsAsErrors gerekiyor; açtığımızda build hatayla bitti, ama paket dosyası yine de yazılmıştı. Tool paketlerinin metadata'sı MSBuild property'leriyle verilir; paket yayımlamanın ayrıntıları Versioning ve Release Management serisinde.
dnx: Kurmadan Çalıştırmak
.NET 10 ile gelen dnx, bir tool'u manifest'e eklemeden indirip çalıştırıyor; dotnet tool exec'in kısa yolu. Yakında bir manifest varsa oradaki sürümü kullanıyor:
cd samples/reporting
dnx Bookstore.Admin isbn 978-0-441-17271-9
valid: 9780441172719
Aynı komutu repo kökünde çalıştırdığımızda sonuç farklıydı:
bookstore.admin is not found in NuGet feeds https://api.nuget.org/v3/index.json.
Kökteki nuget.config yalnız nuget.org'u tanıyor. Paketin klasörünü komut satırından eklemek de işe yaramadı:
dnx Bookstore.Admin --add-source artifacts/package/release info
The --add-source option cannot be combined with package source mapping. To use an additional source, update your NuGet configuration file's source mapping settings. Learn more: https://aka.ms/nuget-package-source-mapping
Bu iyi bir sonuç. dnx internetten bir paket indirip içindeki kodu senin hesabınla çalıştırıyor; source mapping de hangi paket adının hangi feed'den gelebileceğine karar veriyor. Komut satırından bu kuralı delmek mümkün olsaydı, 4. yazıda anlattığımız dependency confusion saldırısı için bir kapı açılırdı. .NET 11'in sürüm notlarına göre dnx ve dotnet tool exec artık ek bir onay sormuyor; paketin nereden geldiğini kontrol eden tek şey NuGet configuration'ın. Bir paket adını dnx'e yazmadan önce, onu bir PackageReference'a yazarken gösterdiğin dikkati göster.
dotnet new Template'i: bookstore-module
Bookstore'a her yeni module eklendiğinde aynı iskelet elle kuruluyor: src/ altında bir library, servisleri kaydeden ve endpoint'leri map eden bir static class, tests/ altında bir test projesi, solution'a iki satır. Bunu bir template'e çevirdik. Template, gerçek dosyalardan ve bir .template.config/template.json'dan oluşuyor:
templates/Bookstore.Templates/
Bookstore.Templates.csproj
content/bookstore-module/
.template.config/template.json
.template.config/dotnetcli.host.json
src/Bookstore.ModuleName/Bookstore.ModuleName.csproj
src/Bookstore.ModuleName/ModuleNameModule.cs
tests/Bookstore.ModuleName.Tests/Bookstore.ModuleName.Tests.csproj
tests/Bookstore.ModuleName.Tests/ModuleNameModuleTests.cs
{
"$schema": "http://json.schemastore.org/template",
"author": "Bookstore",
"classifications": [ "Bookstore", "Library" ],
"identity": "Bookstore.Module",
"name": "Bookstore module",
"shortName": "bookstore-module",
"sourceName": "ModuleName",
"preferNameDirectory": false,
"tags": { "language": "C#", "type": "project" },
"symbols": {
"includeTests": {
"type": "parameter",
"datatype": "bool",
"defaultValue": "true",
"description": "Also create a test project under tests/."
}
},
"sources": [
{
"modifiers": [
{ "condition": "(!includeTests)", "exclude": [ "tests/**" ] }
]
}
],
"primaryOutputs": [
{ "path": "src/Bookstore.ModuleName/Bookstore.ModuleName.csproj" },
{ "condition": "(includeTests)", "path": "tests/Bookstore.ModuleName.Tests/Bookstore.ModuleName.Tests.csproj" }
],
"postActions": [
{
"description": "Add the projects to Bookstore.slnx",
"actionId": "D396686C-DE0E-4DE6-906D-291CD29FC5DE",
"continueOnError": true,
"manualInstructions": [ { "text": "Run dotnet sln add for the new projects." } ]
}
]
}
sourceName, template'in kalbi: dosya adlarında, klasör adlarında ve dosya içeriğinde geçen ModuleName, -n ile verilen adla değişiyor. Template engine küçük harfli biçimi de değiştiriyor; route'taki /api/modulename kendiliğinden /api/shipping oluyor. includeTests parametresi tests/ klasörünü koşullu yapıyor. postActions'taki GUID (genel benzersiz kimlik), template engine'in "projeleri solution'a ekle" işleminin sabit kimliği; eklenecek projeleri primaryOutputs'tan alıyor. dotnetcli.host.json ise parametrenin komut satırındaki adını --include-tests yapıyor.
Template'in içindeki dosyalar sıradan, derlenebilir C# dosyaları. Module'ün iskeleti:
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.DependencyInjection;
namespace Bookstore.ModuleName;
public static class ModuleNameModule
{
public static IServiceCollection AddModuleNameModule(this IServiceCollection services) => services;
public static IEndpointRouteBuilder MapModuleNameEndpoints(this IEndpointRouteBuilder app)
{
RouteGroupBuilder group = app.MapGroup("/api/modulename").WithTags("ModuleName");
group.MapGet("/status", () => TypedResults.Ok("ModuleName module is running"));
return app;
}
}
Üretilen proje dosyası yalnız FrameworkReference taşıyor; TargetFramework, Nullable, analyzer'lar ve artifacts düzeni 3. yazıdaki Directory.Build.props'tan geliyor. Template'in repo kurallarını tekrar yazmaması, kurallar değiştiğinde template'i güncellemek zorunda kalmamak demek.
Template Paketini Hazırlamak ve Test Etmek
Template'i ekibe dağıtmak için onu da bir NuGet paketine koyuyoruz. İlk denememiz şu proje dosyasıydı, son iki property olmadan:
<PropertyGroup>
<PackageType>Template</PackageType>
<PackageId>Bookstore.Templates</PackageId>
<Version>1.0.0</Version>
<Description>dotnet new templates for the Bookstore solution.</Description>
<!-- Paket yalnız content klasörünü taşır: assembly ve derlenmiş kod yok. -->
<IncludeBuildOutput>false</IncludeBuildOutput>
<!-- Template paketinde lib klasörü ve bağımlılık yok; yoksa NU5128. -->
<SuppressDependenciesWhenPacking>true</SuppressDependenciesWhenPacking>
<IncludeContentInPack>true</IncludeContentInPack>
<ContentTargetFolders>content</ContentTargetFolders>
<!-- Bu olmadan NuGet .template.config gibi nokta ile başlayan klasörleri atar. -->
<NoDefaultExcludes>true</NoDefaultExcludes>
</PropertyGroup>
<ItemGroup>
<Content Include="content/**" Exclude="content/**/bin/**;content/**/obj/**" />
<!-- content/ altındaki .cs dosyaları template metni, bu projenin parçası değil. -->
<Compile Remove="content/**" />
</ItemGroup>
Compile Remove olmadan ilk dotnet pack on bir derleme hatası verdi:
C:\src\Bookstore\templates\Bookstore.Templates\content\bookstore-module\src\Bookstore.ModuleName\ModuleNameModule.cs(1,17): error CS0234: The type or namespace name 'AspNetCore' does not exist in the namespace 'Microsoft' (are you missing an assembly reference?) [C:\src\Bookstore\templates\Bookstore.Templates\Bookstore.Templates.csproj]
SDK'nın **/*.cs glob'u template içeriğini de paket projesinin kaynak kodu sandı. Onu çıkarınca sıradaki hata NuGet'ten geldi:
C:\Program Files\dotnet\sdk\10.0.404\NuGet.Build.Tasks.Pack.targets(226,5): error NU5128: Warning As Error: Some target frameworks declared in the dependencies group of the nuspec and the lib/ref folder do not have exact matches in the other location. Consult the list of actions below: [C:\src\Bookstore\templates\Bookstore.Templates\Bookstore.Templates.csproj]
C:\Program Files\dotnet\sdk\10.0.404\NuGet.Build.Tasks.Pack.targets(226,5): error NU5128: - Add lib or ref assemblies for the net10.0 target framework [C:\src\Bookstore\templates\Bookstore.Templates\Bookstore.Templates.csproj]
Paketin lib klasörü yok ama bağımlılık listesinde net10.0 grubu var; ikisi tutarsız. NuGet uyarısı TreatWarningsAsErrors yüzünden hata oldu. SuppressDependenciesWhenPacking bağımlılık grubunu kaldırdı ve paket oluştu. Bu iki hata, repodaki ortak ayarların template projesine de uygulandığını gösteriyor; template paketini repodan ayrı bir yerde hazırlasaydık ikisini de görmeyecektik.
dotnet new install artifacts/package/release/Bookstore.Templates.1.0.0.nupkg
dotnet new bookstore-module -n Shipping --dry-run
dotnet new bookstore-module -n Shipping
File actions would have been taken:
Create: ./src/Bookstore.Shipping/Bookstore.Shipping.csproj
Create: ./src/Bookstore.Shipping/ShippingModule.cs
Create: ./tests/Bookstore.Shipping.Tests/Bookstore.Shipping.Tests.csproj
Create: ./tests/Bookstore.Shipping.Tests/ShippingModuleTests.cs
...
The template "Bookstore module" was created successfully.
Processing post-creation actions...
Adding
project(s): C:\src\Bookstore\src\Bookstore.Shipping\Bookstore.Shipping.csproj C:\src\Bookstore\tests\Bookstore.Shipping.Tests\Bookstore.Shipping.Tests.csproj
to solution file: C:\src\Bookstore\Bookstore.slnx
solution folder:
Project `src/Bookstore.Shipping/Bookstore.Shipping.csproj` added to the solution.
Project `tests/Bookstore.Shipping.Tests/Bookstore.Shipping.Tests.csproj` added to the solution.
Successfully added project(s) to a solution file.
--dry-run diske yazmadan ne olacağını gösteriyor; template geliştirirken en çok kullanacağın parametre bu. Asıl çalıştırmada post action iki projeyi Bookstore.slnx'teki /src/ ve /tests/ klasörlerine ekledi. Template'in testi de bu: üretilen kodu derleyip testlerini çalıştırmak.
dotnet build Bookstore.slnx
dotnet test --project tests/Bookstore.Shipping.Tests
Test run summary: Passed!
total: 1
failed: 0
succeeded: 1
skipped: 0
Bu denemeyi repo kopyasında yaptık; Shipping module'ünü Bookstore'a eklemek bu yazının konusu değil. Aynı adımları --include-tests false ile de tekrarladık; yalnız library oluştu ve solution'a tek proje eklendi.
File-Based App'ler: Proje Gerektirmeyen Script'ler
Her araç için bir proje ve paket gerekmiyor. Katalog ekibi yayınevlerinden gelen ISBN (kitapların uluslararası standart numarası) listelerini kontrol etmek istiyor; bunun için scripts/ klasörüne tek dosyalık bir program yazdık:
#!/usr/bin/env dotnet
#:project ../src/Bookstore.Contracts/Bookstore.Contracts.csproj
#:include IsbnReport.cs
using Bookstore.Contracts;
// Her satırda bir ISBN okur, geçersizleri listeler; geçersiz varsa exit code 1.
IsbnReport report = new();
foreach (string line in File.ReadLines(args[0]))
{
report.Add(line, Isbn.TryParse(line, out _));
}
report.Print();
return report.InvalidCount == 0 ? 0 : 1;
dotnet run scripts/check-isbns.cs -- scripts/isbns.txt
invalid: 978-0-441-17271-0
1 invalid ISBN(s)
#:project, script'e Bookstore.Contracts'ı bağlıyor; ISBN kontrolünü tekrar yazmıyoruz. File-based app repo içindeyse sıradan bir proje gibi davranıyor: Directory.Build.props, Directory.Packages.props, nuget.config ve global.json ona da uygulanıyor. Bunu bir denemede gördük: script'e DateTime.Now yazınca 3. yazıda kurduğumuz banned API analyzer'ı devreye girdi:
C:\src\Bookstore\scripts\now.cs(1,42): error RS0030: The symbol 'DateTime.Now' is banned in this project: Inject TimeProvider instead (https://github.com/dotnet/roslyn/blob/main/src/RoslynAnalyzers/Microsoft.CodeAnalysis.BannedApiAnalyzers/BannedApiAnalyzers.Help.md)
Script'ler için farklı kurallar istiyorsan scripts/ altına ayrı bir Directory.Build.props koyabilirsin. Build çıktısı da repodaki artifacts düzenine gitti: dotnet build scripts/check-isbns.cs -getProperty:OutputPath artifacts/bin/debug/ döndü. File-based app'lerde PublishAot varsayılan olarak açık; script'i publish edersen Native AOT executable çıkıyor.
Rapor sınıfını #:include ile ayrı dosyaya aldık. Microsoft'un dokümanına göre bu directive .NET 11'de ve SDK 10.0.300 ile geldi; gerçekten de 10.0.115'te script şu hatayla durdu:
C:\src\Bookstore\scripts\check-isbns.cs(3): error: Unrecognized directive 'include'.
Bookstore'un global.json'u 10.0.4xx bandını istediği için repoda sorun yok, ama feature band farkının bir script'i bile kırabildiğini görmek 1. yazıdaki SDK sabitlemesinin neden önemli olduğunu tekrar gösteriyor. İlk sürümde shebang satırı yoktu ve build şu hatayla bitti:
C:\src\Bookstore\scripts\check-isbns.cs(2,1): error CA2266: File-based program entry point should start with '#!' (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2266)
Birden fazla dosyalı bir file-based app'te CA2266, giriş dosyasının #! ile başlamasını istiyor; böylece araçlar hangi dosyanın program, hangisinin include edilen parça olduğunu ayırabiliyor. Windows'ta shebang satırının bir etkisi yok, dotnet run onu atlıyor. Script büyüyüp birkaç dosyayı, paketleri ve testleri aşınca dotnet project convert scripts/check-isbns.cs onu sıradan bir projeye çeviriyor.
Üretimde Dikkat
CI pipeline'ının ilk adımı dotnet tool restore olsun ve tool sürümleri manifest'te sabit kalsın. Bir aracı yükseltmek, bir paketi yükseltmek gibi bir pull request ile yapılır; CI'da "en son sürümü kur" diyen bir satır, build'in bir gün sebepsiz değişmesi demek.
Tool paketleri de bağımlılıktır. Feed'e kimin push edebildiğini, paketlerin hangi kaynaktan geldiğini ve source mapping'i, kütüphane paketleri için ne kadar sıkı tutuyorsan araçlar için de o kadar sıkı tut. dnx ile tek seferlik çalıştırılan bir paket bile makinede kod çalıştırır.
Template'ler de kod gibi test edilir. Template paketini CI'da pack edip bir repo kopyasına kuran, her parametre kombinasyonuyla üretip build ve test çalıştıran bir job, template'in repo kurallarıyla uyumlu kaldığını gösterir. Directory.Build.props'a yeni bir analyzer kuralı eklendiğinde ilk kırılacak yer genellikle template çıktısıdır.
Sık Düşülen Tuzaklar
Global tool'a bağımlı CI script'i. Geliştiricinin makinesinde çalışan script, temiz bir agent'ta "command not found" ile biter. Araçları local manifest'e koy, script'i dotnet tool restore ile başlat.
Template'i test etmeden yayımlamak. Bizim template'imiz ilk pack'te on bir derleme hatası ve bir NuGet hatası verdi; bunlar paket yayımlandıktan sonra ekip arkadaşının makinesinde çıksaydı, sorunun template'te olduğunu anlamak çok daha uzun sürerdi.
Tool paketine custom .nuspec vermek. Paket oluşur ama tool olarak çalışmaz; SDK 11'in NETSDK1235 uyarısı da TreatWarningsAsErrors ile hataya dönmez.
Kurulan aracın klasöründeki appsettings.json'u düzenlemek. O klasör NuGet cache'idir; bir sonraki restore ya da sürüm değişikliğinde değişikliğin kaybolur. Ortama göre değişen ayarları ortam değişkeniyle ver.
File-based app'i eski bir feature band'de çalıştırmak. #:include gibi directive'ler SDK bandına bağlı; script'in bulunduğu klasörü bir global.json kapsamıyorsa makinedeki en yeni SDK seçilir ve sonuç makineden makineye değişir.
Küçük Bir Deneme
★☆☆ Repo kökünde dotnet tool restore çalıştır, sonra dotnet tool list ile manifest'teki araçları gör. dotnet-tools.json'da dotnet-ef'in sürümünü değiştirip tekrar restore et ve dotnet ef --version çıktısını karşılaştır.
★★☆ bookstore-module template'ine --with-endpoints adında bir bool parametre ekle; false olduğunda MapModuleNameEndpoints method'u üretilmesin. İpucu: C# dosyalarında //#if (withEndpoints) biçiminde koşullu bölgeler kullanılabiliyor. İki değerle de üretip build ve test çalıştır.
★★★ bookstore-admin'in API adresini BOOKSTORE_API_BASE_URL ortam değişkeninden okumasını sağla, sürümü 1.1.0 yap, paketi feed'e push et ve Reporting manifest'ini dotnet tool update Bookstore.Admin ile güncelle. Manifest'teki değişikliği bir pull request'te nasıl görüneceğine göre değerlendir.
Bu yazıda araçları ve template'leri paketledik; her birinde SDK'nın arka planda çalıştırdığı target'lara dokunduk: pack'in publish'i çağırması, glob'un template içeriğini derlemesi, bir uyarının hata olup olmaması. Sıradaki yazıda bu katmanın kendisine iniyoruz: MSBuild'in property, item, target ve task kavramları, kendi target'ımızı yazmak ve binlog ile bir build'in içinde ne olduğunu görmek.