06. NuGet publish flow
This note documents the Mochi-as-.NET-library publish path: how mochi pkg publish --to=nuget.org lowers a Mochi package to a publishable .NET NuGet package and uploads it.
The library package shape
A .NET NuGet package intended for nuget.org distribution has the following structure:
mochi-emitted.nupkg (a ZIP archive)
[Content_Types].xml
_rels/.rels
package/
services/
metadata/
core-properties/
<guid>.psmdcp
MyMochiLib.nuspec # the package manifest
lib/
net8.0/
MyMochiLib.dll # the compiled .NET assembly
MyMochiLib.xml # XML documentation (optional)
icon.png # optional
README.md # optional
The user writes Mochi in <package>/src/*.mochi. The bridge lowers via TargetDotNetLibrary (a new MEP-53 build target) to a C# project, compiles it with dotnet build -c Release, and packages it with dotnet pack.
The emitted .csproj:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<AssemblyName>MyMochiLib</AssemblyName>
<RootNamespace>MyMochiLib</RootNamespace>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<PackageId>MyMochiLib</PackageId>
<PackageVersion>1.0.0</PackageVersion>
<Authors>tamnd</Authors>
<Description>A Mochi library published as a NuGet package.</Description>
<PackageProjectUrl>https://github.com/example/mymochi</PackageProjectUrl>
<PackageLicenseExpression>Apache-2.0</PackageLicenseExpression>
<PackageReadmeFile>README.md</PackageReadmeFile>
<PackageTags>mochi</PackageTags>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<None Include="README.md" Pack="true" PackagePath="\" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="mochi-runtime-dotnet" Version="0.6.0" />
</ItemGroup>
</Project>
Metadata fields come from the Mochi mochi.toml [dotnet.publish] table. The package name is the Mochi package name (with @scope/name form, if present, flattened to scope.name since NuGet does not support scoped package identifiers).
src/MyMochiLib.cs is generated by lowering the Mochi package's public API. Mochi fun items become public static methods. Mochi record items become public record struct items. Mochi type T = A | B sum types become public abstract class T with nested derived types A and B. Private items become internal.
NuGet v3 API and resolution
The NuGet v3 API is a RESTful JSON API documented at https://docs.microsoft.com/en-us/nuget/api/overview. The bridge uses the following endpoints:
- Service index (
https://api.nuget.org/v3/index.json): returns a JSON document listing all v3 API endpoints. The bridge fetches this at lock time. - Search query service (a URL from the service index with resource type
SearchQueryService/3.5.0): resolves package name + version constraint to a specific version. - Flat container (resource type
PackageBaseAddress/3.0.0): the content-addressed.nupkgdownload URL, in the form<baseUrl>/<id>/<version>/<id>.<version>.nupkg. - Registration pages (resource type
RegistrationsBaseUrl/3.6.0): package metadata, including.nuspecdependency groups, deprecation status, and vulnerability advisories.
The resolution process:
- Fetch the service index.
- For each
[dotnet-dependencies]entry, query the search API with the package name and version constraint. - Select the highest satisfying version (NuGet's default strategy; overridable via
[dotnet.resolve] strategy = "lowest"inmochi.toml). - Fetch the registration page for the resolved version to get the
.nuspecdependency groups. - Recurse into transitive dependencies until the full graph is resolved.
- Download each
.nupkgto~/.cache/mochi/dotnet-deps/<sha512-hex>/.
The .nuspec manifest
A .nuspec file is an XML manifest inside the .nupkg that declares package metadata:
<?xml version="1.0" encoding="utf-8"?>
<package xmlns="http://schemas.microsoft.com/packaging/2013/05/nuspec.xsd">
<metadata>
<id>MyMochiLib</id>
<version>1.0.0</version>
<authors>tamnd</authors>
<description>A Mochi library published as a NuGet package.</description>
<projectUrl>https://github.com/example/mymochi</projectUrl>
<license type="expression">Apache-2.0</license>
<readme>README.md</readme>
<tags>mochi</tags>
<dependencies>
<group targetFramework="net8.0">
<dependency id="mochi-runtime-dotnet" version="0.6.0" exclude="Build,Analyzers" />
</group>
</dependencies>
</metadata>
</package>
The bridge generates this manifest from [dotnet.publish] in mochi.toml. Required fields: id, version, authors, description, license. Optional but strongly recommended: projectUrl, readme, tags.
The mochi pkg publish --to=nuget.org flow
$ mochi pkg publish --to=nuget.org
[1/6] Lowering package via TargetDotNetLibrary ... (2.1s)
[2/6] Compiling C# project ... (4.3s)
[3/6] Packing .nupkg ... (1.0s)
[4/6] Validating .nuspec metadata ... OK
[5/6] Obtaining GitHub Actions OIDC token (audience: nuget.org) ...
sub: repo:example/mymochi:ref:refs/tags/v1.0.0
[6/6] Uploading to nuget.org ... OK
Package published: https://www.nuget.org/packages/MyMochiLib/1.0.0
The --dry-run flag skips step 6:
$ mochi pkg publish --to=nuget.org --dry-run
...
[6/6] DRY RUN: would POST .nupkg to https://www.nuget.org/api/v2/package
Package: MyMochiLib 1.0.0
SHA-512: abc123...
nuget.org upload protocol
The nuget.org upload endpoint (v2-compatible, the currently supported endpoint) is:
PUT https://www.nuget.org/api/v2/package
Authorization: <token> (legacy) or X-NuGet-Protocol-Version: 4.1.0 (trusted publishing)
Content-Type: application/octet-stream
Body: raw .nupkg bytes
For trusted publishing (see 07-nuget-trusted-publishing), the Authorization header carries the OIDC token:
Authorization: Bearer <oidc-token>
X-NuGet-Protocol-Version: 4.1.0
nuget.org's server-side handler validates the OIDC token against the configured trusted publisher and processes the upload.
Metadata validation
Before upload the bridge validates:
package-idmatches the NuGet package name regex[A-Za-z0-9_\-.]{1,64}(case-insensitive; no@scope/form).versionparses as NuGet semver (SemVer 2.0.0 compliant).licenseparses as an SPDX expression.descriptionis between 1 and 4000 characters.- The
README.mdfile exists in the package root. - No file in the package exceeds 100 MB (nuget.org's per-file limit).
- The total
.nupkgsize does not exceed 250 MB (nuget.org's package limit).
A validation failure exits before the upload step.
TargetDotNetLibrary emit path
TargetDotNetLibrary is a new MEP-53 build target. The driver's Build function gates on Driver.LibraryMode=true plus target == TargetDotNetLibrary. The emit path:
- Lowers the Mochi package's public surface to a C# file (
src/<PackageId>.cs) containingpublic staticmethods,public record structtypes, andpublic abstract classsum types. - Generates the
.csprojfrom[dotnet.publish]. - Runs
dotnet build -c Release. - Runs
dotnet pack -c Release --no-build. - Returns the path to the generated
.nupkg.
The emit step preserves Mochi's module visibility model: only pub items in the Mochi package become public in the generated C#; non-pub items become internal.
Cross-references
- 01-language-surface for the
mochi pkg publishCLI surface. - 07-nuget-trusted-publishing for the OIDC token exchange.
- 09-abi-stability for the .NET assembly ABI that downstream C# consumers link against.
- MEP-53 for the build driver that
TargetDotNetLibraryextends. - MEP-68 §5 for the normative CLI surface.