关于 dotnet-cli-packaging
dotnet-cli-packaging 是面向 .NET 命令行工具维护者的打包技能,帮助准备分发包。它提供 Homebrew、apt/deb、winget、Scoop、Chocolatey、dotnet tool 和 NuGet 格式的指南与示例。
主要功能
- 01创建 Homebrew formula 和 cask
- 02用 `dpkg-deb` 构建 apt/deb 包
- 03准备 winget 和 Scoop 清单
- 04为 Chocolatey 打包工具
- 05配置 `dotnet tool` 包
- 06向 NuGet 发布包
适用场景
- 01为 Homebrew 打包 .NET 命令行工具
- 02准备 Debian 或 Ubuntu 的 `.deb` 发布包
- 03向 winget 或 Scoop 提交命令行工具包
- 04通过 dotnet tool 或 NuGet 分发工具
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ wshaddix/ dotnet-skills --skill dotnet-cli-packaging安装后在 Claude Code 中输入/dotnet-cli-packaging确认已加载。
详细介绍
dotnet-cli-packaging
面向多平台的 .NET CLI 工具打包:Homebrew 公式编写(二进制 tap 和 cask)、使用 dpkg-deb 打包 apt/deb、winget 清单 YAML 架构及向 winget-pkgs 提交 PR、Scoop 清单 JSON、创建 Chocolatey 包、dotnet tool 全局/本地打包,以及 NuGet 分发。
**版本假设:**基准版本为 .NET 8.0+。各包管理器的格式在不同 .NET 版本间保持稳定。
**不在范围内:**CLI 分发策略(AOT、依赖框架运行或选择 dotnet tool)——参见 [skill:dotnet-cli-distribution]。自动化打包的发布 CI/CD 流水线——参见 [skill:dotnet-cli-release-pipeline]。Native AOT 编译——参见 [skill:dotnet-native-aot]。基于容器的分发——参见 [skill:dotnet-containers]。通用 CI/CD 模式——参见 [skill:dotnet-gha-patterns] 和 [skill:dotnet-ado-patterns]。
交叉引用:[skill:dotnet-cli-distribution] 介绍分发策略和 RID 矩阵,[skill:dotnet-cli-release-pipeline] 介绍自动发布软件包,[skill:dotnet-native-aot] 介绍 AOT 二进制文件生成,[skill:dotnet-containers] 介绍基于容器的分发,[skill:dotnet-tool-management] 介绍用户侧的工具安装和清单管理。
Homebrew(macOS / Linux)
Homebrew 是 macOS 的主要包管理器,在 Linux 上也被广泛使用。CLI 工具有两种分发格式。
二进制 Tap(公式)
公式会按平台下载预构建的二进制文件。这是 Native AOT CLI 工具的推荐方式。
# Formula/mytool.rb
class Mytool < Formula
desc "A CLI tool for managing widgets"
homepage "https://github.com/myorg/mytool"
version "1.2.3"
license "MIT"
on_macos do
on_arm do
url "https://github.com/myorg/mytool/releases/download/v1.2.3/mytool-1.2.3-osx-arm64.tar.gz"
sha256 "abc123..."
end
# Optional: remove on_intel block if not targeting Intel Macs
on_intel do
url "https://github.com/myorg/mytool/releases/download/v1.2.3/mytool-1.2.3-osx-x64.tar.gz"
sha256 "def456..."
end
end
on_linux do
on_arm do
url "https://github.com/myorg/mytool/releases/download/v1.2.3/mytool-1.2.3-linux-arm64.tar.gz"
sha256 "ghi789..."
end
on_intel do
url "https://github.com/myorg/mytool/releases/download/v1.2.3/mytool-1.2.3-linux-x64.tar.gz"
sha256 "jkl012..."
end
end
def install
bin.install "mytool"
end
test do
assert_match version.to_s, shell_output("#{bin}/mytool --version")
end
end
托管 Tap
Tap 是包含公式的 Git 仓库。创建一个名为 homebrew-tap 的仓库:
myorg/homebrew-tap/
Formula/
mytool.rb
用户通过以下方式安装:
brew tap myorg/tap
brew install mytool
Homebrew Cask
Cask 适用于 GUI 应用或带安装程序的工具。对于纯 CLI 工具,优先使用公式,而不是 cask。
# Casks/mytool.rb -- only if the tool has a GUI component
cask "mytool" do
version "1.2.3"
sha256 "abc123..."
url "https://github.com/myorg/mytool/releases/download/v#{version}/mytool-#{version}-osx-arm64.tar.gz"
name "MyTool"
homepage "https://github.com/myorg/mytool"
binary "mytool"
end
apt/deb(Debian/Ubuntu)
使用 dpkg-deb 构建 .deb 软件包
创建软件包目录结构:
mytool_1.2.3_amd64/
DEBIAN/
control
usr/
bin/
mytool
Control 文件:
Package: mytool
Version: 1.2.3
Section: utils
Priority: optional
Architecture: amd64
Maintainer: My Org <[email protected]>
Description: A CLI tool for managing widgets
MyTool provides fast widget management from the command line.
Built with .NET Native AOT for zero-dependency execution.
Homepage: https://github.com/myorg/mytool
构建软件包:
#!/bin/bash
set -euo pipefail
VERSION="${1:?Usage: build-deb.sh <version>}"
ARCH="amd64" # or arm64
PKG_DIR="mytool_${VERSION}_${ARCH}"
mkdir -p "$PKG_DIR/DEBIAN"
mkdir -p "$PKG_DIR/usr/bin"
# Copy the published binary
cp "artifacts/linux-x64/mytool" "$PKG_DIR/usr/bin/mytool"
chmod 755 "$PKG_DIR/usr/bin/mytool"
# Write control file
cat > "$PKG_DIR/DEBIAN/control" << EOF
Package: mytool
Version: ${VERSION}
Section: utils
Priority: optional
Architecture: ${ARCH}
Maintainer: My Org <[email protected]>
Description: A CLI tool for managing widgets
Homepage: https://github.com/myorg/mytool
EOF
# Build the .deb
dpkg-deb --build --root-owner-group "$PKG_DIR"
echo "Built: ${PKG_DIR}.deb"
RID 到 Debian 架构的映射:
| .NET RID | Debian 架构 |
|---|---|
linux-x64 | amd64 |
linux-arm64 | arm64 |
安装 .deb
sudo dpkg -i mytool_1.2.3_amd64.deb
winget(Windows 包管理器)
清单 YAML 架构
winget 清单由多个 YAML 文件组成,存放在 microsoft/winget-pkgs 仓库的版本化目录结构中。
目录结构:
manifests/
m/
MyOrg/
MyTool/
1.2.3/
MyOrg.MyTool.yaml # Version manifest
MyOrg.MyTool.installer.yaml # Installer manifest
MyOrg.MyTool.locale.en-US.yaml # Locale manifest
版本清单(MyOrg.MyTool.yaml):
PackageIdentifier: MyOrg.MyTool
PackageVersion: 1.2.3
DefaultLocale: en-US
ManifestType: version
ManifestVersion: 1.9.0
安装程序清单(MyOrg.MyTool.installer.yaml):
PackageIdentifier: MyOrg.MyTool
PackageVersion: 1.2.3
InstallerType: zip
NestedInstallerType: portable
NestedInstallerFiles:
- RelativeFilePath: mytool.exe
PortableCommandAlias: mytool
Installers:
- Architecture: x64
InstallerUrl: https://github.com/myorg/mytool/releases/download/v1.2.3/mytool-1.2.3-win-x64.zip
InstallerSha256: ABC123...
- Architecture: arm64
InstallerUrl: https://github.com/myorg/mytool/releases/download/v1.2.3/mytool-1.2.3-win-arm64.zip
InstallerSha256: DEF456...
ManifestType: installer
ManifestVersion: 1.9.0
区域设置清单(MyOrg.MyTool.locale.en-US.yaml):
PackageIdentifier: MyOrg.MyTool
PackageVersion: 1.2.3
PackageLocale: en-US
PackageName: MyTool
Publisher: My Org
ShortDescription: A CLI tool for managing widgets
License: MIT
PackageUrl: https://github.com/myorg/mytool
ManifestType: defaultLocale
ManifestVersion: 1.9.0
提交到 winget-pkgs
- 在 GitHub 上 Fork
microsoft/winget-pkgs - 在正确的目录结构中创建清单文件
- 在本地验证:
winget validate --manifest <path> - 提交 PR——系统会针对清单运行自动检查
- Microsoft 团队审核并合并
参见 [skill:dotnet-cli-release-pipeline],了解如何自动创建 winget PR。
Scoop(Windows)
Scoop 在 Windows 高级用户中很受欢迎。清单是存放在 bucket 仓库中的 JSON 文件。
Scoop 清单
{
"version": "1.2.3",
"description": "A CLI tool for managing widgets",
"homepage": "https://github.com/myorg/mytool",
"license": "MIT",
"architecture": {
"64bit": {
"url": "https://github.com/myorg/mytool/releases/download/v1.2.3/mytool-1.2.3-win-x64.zip",
"hash": "abc123..."
},
"arm64": {
"url": "https://github.com/myorg/mytool/releases/download/v1.2.3/mytool-1.2.3-win-arm64.zip",
"hash": "def456..."
}
},
"bin": "mytool.exe",
"checkver": {
"github": "https://github.com/myorg/mytool"
},
"autoupdate": {
"architecture": {
"64bit": {
"url": "https://github.com/myorg/mytool/releases/download/v$version/mytool-$version-win-x64.zip"
},
"arm64": {
"url": "https://github.com/myorg/mytool/releases/download/v$version/mytool-$version-win-arm64.zip"
}
}
}
}
托管 Scoop Bucket
创建一个名为 scoop-mytool(或 scoop-bucket)的 GitHub 仓库:
myorg/scoop-mytool/
bucket/
mytool.json
用户通过以下方式安装:
scoop bucket add myorg https://github.com/myorg/scoop-mytool
scoop install mytool
Chocolatey
Chocolatey 是 Windows 上历史最悠久的二进制分发包管理器。
软件包结构
mytool/
mytool.nuspec
tools/
chocolateyInstall.ps1
LICENSE.txt
mytool.nuspec:
<?xml version="1.0" encoding="utf-8"?>
<package xmlns="http://schemas.xmldata.org/2004/07/nuspec">
<metadata>
<id>mytool</id>
<version>1.2.3</version>
<title>MyTool</title>
<authors>My Org</authors>
<projectUrl>https://github.com/myorg/mytool</projectUrl>
<license type="expression">MIT</license>
<description>A CLI tool for managing widgets.</description>
<tags>cli dotnet tools</tags>
</metadata>
</package>
tools/chocolateyInstall.ps1:
$ErrorActionPreference = 'Stop'
$packageArgs = @{
packageName = 'mytool'
url64bit = 'https://github.com/myorg/mytool/releases/download/v1.2.3/mytool-1.2.3-win-x64.zip'
checksum64 = 'ABC123...'
checksumType64 = 'sha256'
unzipLocation = "$(Split-Path -Parent $MyInvocation.MyCommand.Definition)"
}
Install-ChocolateyZipPackage @packageArgs
构建和发布
# Pack the .nupkg
choco pack mytool.nuspec
# Test locally
choco install mytool --source="." --force
# Push to Chocolatey Community Repository
choco push mytool.1.2.3.nupkg --source https://push.chocolatey.org/ --api-key $env:CHOCO_API_KEY
dotnet tool(全局和本地)
对于 .NET 开发者来说,dotnet tool 是最简单的分发方式。工具以 NuGet 软件包的形式分发。
工具打包的项目配置
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<PackAsTool>true</PackAsTool>
<ToolCommandName>mytool</ToolCommandName>
<PackageId>MyOrg.MyTool</PackageId>
<Version>1.2.3</Version>
<Description>A CLI tool for managing widgets</Description>
<Authors>My Org</Authors>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageProjectUrl>https://github.com/myorg/mytool</PackageProjectUrl>
<PackageReadmeFile>README.md</PackageReadmeFile>
</PropertyGroup>
<ItemGroup>
<None Include="../../README.md" Pack="true" PackagePath="/" />
</ItemGroup>
</Project>
构建和发布
# Pack the tool
dotnet pack -c Release
# Publish to NuGet.org
dotnet nuget push bin/Release/MyOrg.MyTool.1.2.3.nupkg \
--source https://api.nuget.org/v3/index.json \
--api-key "$NUGET_API_KEY"
安装 dotnet 工具
# Global tool (available system-wide)
dotnet tool install -g MyOrg.MyTool
# Local tool (per-project, tracked in .config/dotnet-tools.json)
dotnet new tool-manifest # first time only
dotnet tool install MyOrg.MyTool
# Update
dotnet tool update -g MyOrg.MyTool
# Run local tool
dotnet tool run mytool
# or just:
dotnet mytool
全局工具与本地工具
| 方面 | 全局工具 | 本地工具 |
|---|---|---|
| 范围 | 系统范围(按用户) | 按项目目录 |
| 安装位置 | ~/.dotnet/tools | .config/dotnet-tools.json |
| 版本管理 | 手动更新 | 在源代码管理中跟踪 |
| CI/CD | 使用前必须安装 | dotnet tool restore 会还原所有工具 |
| 最适用于 | 个人效率工具 | 项目专用构建工具 |
NuGet 分发
对于通过 NuGet 包分发的工具(无论是作为 dotnet tool 还是独立工具):
包元数据
<PropertyGroup>
<PackageId>MyOrg.MyTool</PackageId>
<Version>1.2.3</Version>
<Description>A CLI tool for managing widgets</Description>
<Authors>My Org</Authors>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageProjectUrl>https://github.com/myorg/mytool</PackageProjectUrl>
<PackageReadmeFile>README.md</PackageReadmeFile>
<PackageTags>cli;tools;widgets</PackageTags>
<RepositoryUrl>https://github.com/myorg/mytool</RepositoryUrl>
<RepositoryType>git</RepositoryType>
</PropertyGroup>
发布到 NuGet.org
# Pack
dotnet pack -c Release -o ./nupkgs
# Push (use env var for API key -- never hardcode)
dotnet nuget push ./nupkgs/MyOrg.MyTool.1.2.3.nupkg \
--source https://api.nuget.org/v3/index.json \
--api-key "$NUGET_API_KEY"
私有源分发
# Push to a private feed (Azure Artifacts, GitHub Packages, etc.)
dotnet nuget push ./nupkgs/MyOrg.MyTool.1.2.3.nupkg \
--source https://pkgs.dev.azure.com/myorg/_packaging/myfeed/nuget/v3/index.json \
--api-key "$AZURE_ARTIFACTS_PAT"
包格式比较
| 格式 | 平台 | 是否需要 .NET | 自动更新 | 难度 |
|---|---|---|---|---|
| Homebrew formula | macOS、Linux | 否(二进制 tap) | brew upgrade | 中 |
| apt/deb | Debian/Ubuntu | 否(AOT 二进制) | 通过 apt 仓库 | 中 |
| winget | Windows 10+ | 否(便携式) | winget upgrade | 中 |
| Scoop | Windows | 否(便携式) | scoop update | 低 |
| Chocolatey | Windows | 否 | choco upgrade | 中 |
| dotnet tool | 跨平台 | 是(SDK) | dotnet tool update | 低 |
| NuGet(库) | 跨平台 | 是(SDK) | NuGet 还原 | 低 |
Agent 常见问题
- 不要在包清单中硬编码 SHA-256 哈希值。 根据实际发布产物生成校验和,而不是使用占位值。所有包管理器都会验证下载文件的校验和。
- 不要在 winget 中为便携式 CLI 工具使用
InstallerType: exe。 独立可执行文件应使用InstallerType: zip和NestedInstallerType: portable。exe类型表示带有静默标志的安装程序。 - dotnet tool 项目不要漏掉
PackAsTool。 如果没有<PackAsTool>true</PackAsTool>,dotnet pack生成的是库包,而不是可安装的工具。 - 不要在打包脚本中硬编码 API 密钥。 使用环境变量引用(
$NUGET_API_KEY、$env:CHOCO_API_KEY),并添加注释说明 CI 密钥配置。 - 不要为同一个 CLI 工具同时使用 Homebrew formula 和 cask。 纯 CLI 工具应使用 formula。Cask 用于带有 macOS 应用包的 GUI 应用程序。
- 不要跳过 Homebrew formula 中的
test块。 Homebrew CI 会运行 formula 测试。缺少测试块会导致审核被拒。至少应测试--version输出。