Backend Package
The backend handles .NET compilation.
Overview
The backend:
- Generates .csproj project files
- Generates Program.cs entry points
- Invokes dotnet CLI commands
- Handles NativeAOT configuration
Project Generation
generateCsproj
project-generator.ts:
const csproj = generateCsproj({
rootNamespace: "MyApp",
outputName: "app",
dotnetVersion: "net10.0",
outputConfig: {
type: "executable",
nativeAot: true,
singleFile: true,
trimmed: true,
stripSymbols: true,
optimization: "Speed",
invariantGlobalization: true,
selfContained: true,
},
packages: [{ name: "Newtonsoft.Json", version: "13.0.3" }],
assemblyReferences: [
{ name: "Tsonic.Runtime", hintPath: "../runtime/Tsonic.Runtime.dll" },
],
});
Generated .csproj
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<RootNamespace>MyApp</RootNamespace>
<AssemblyName>app</AssemblyName>
<Nullable>enable</Nullable>
<ImplicitUsings>false</ImplicitUsings>
<!-- NativeAOT settings -->
<PublishAot>true</PublishAot>
<PublishSingleFile>true</PublishSingleFile>
<PublishTrimmed>true</PublishTrimmed>
<InvariantGlobalization>true</InvariantGlobalization>
<StripSymbols>true</StripSymbols>
<!-- Optimization -->
<OptimizationPreference>Speed</OptimizationPreference>
<IlcOptimizationPreference>Speed</IlcOptimizationPreference>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
</ItemGroup>
<ItemGroup>
<Reference Include="Tsonic.Runtime">
<HintPath>../runtime/Tsonic.Runtime.dll</HintPath>
</Reference>
</ItemGroup>
</Project>
Program Generation
generateProgramCs
program-generator.ts:
const programCs = generateProgramCs({
namespace: "MyApp",
className: "App",
methodName: "main",
isAsync: false,
needsProgram: true,
});
Generated Program.cs
Sync main:
public static class Program
{
public static void Main(string[] args)
{
global::MyApp.App.main();
}
}
Async main:
public static class Program
{
public static async Task Main(string[] args)
{
await global::MyApp.App.main();
}
}
dotnet CLI Wrapper
checkDotnetInstalled
dotnet.ts:
const checkDotnetInstalled = (): Result<string, string> => {
const result = spawnSync("dotnet", ["--version"]);
if (result.status !== 0) {
return error("dotnet SDK not found");
}
return ok(result.stdout.toString().trim());
};
detectRid
Runtime identifier detection:
const detectRid = (): string => {
const platform = process.platform;
const arch = process.arch;
const platformMap: Record<string, string> = {
linux: "linux",
darwin: "osx",
win32: "win",
};
const archMap: Record<string, string> = {
x64: "x64",
arm64: "arm64",
};
return `${platformMap[platform]}-${archMap[arch]}`;
};
Build Orchestration
Executable Build
const buildExecutable = (config, generatedDir): Result<string, string> => {
// 1. Run dotnet publish
const publishArgs = [
"publish",
"tsonic.csproj",
"-c",
"Release",
"-r",
config.rid,
"--nologo",
];
const result = spawnSync("dotnet", publishArgs, {
cwd: generatedDir,
});
if (result.status !== 0) {
return error(`dotnet publish failed: ${result.stderr}`);
}
// 2. Copy output binary
const publishDir = `${generatedDir}/bin/Release/${config.dotnetVersion}/${config.rid}/publish`;
const sourceBinary = `${publishDir}/${config.outputName}`;
const targetBinary = `out/${config.outputName}`;
copyFileSync(sourceBinary, targetBinary);
chmodSync(targetBinary, 0o755);
return ok(targetBinary);
};
Library Build
const buildLibrary = (config, generatedDir): Result<string, string> => {
// 1. Run dotnet build
const buildArgs = ["build", "tsonic.csproj", "-c", "Release", "--nologo"];
const result = spawnSync("dotnet", buildArgs, {
cwd: generatedDir,
});
// 2. Copy artifacts to dist/
for (const framework of config.targetFrameworks) {
const buildDir = `${generatedDir}/bin/Release/${framework}`;
copyFileSync(`${buildDir}/${config.outputName}.dll`, `dist/${framework}/`);
}
return ok("dist/");
};
Output Types
ExecutableConfig
type ExecutableConfig = {
type: "executable";
nativeAot: boolean;
singleFile: boolean;
trimmed: boolean;
stripSymbols: boolean;
optimization: "Size" | "Speed";
invariantGlobalization: boolean;
selfContained: boolean;
};
LibraryConfig
type LibraryConfig = {
type: "library";
targetFrameworks: string[];
generateDocumentation: boolean;
includeSymbols: boolean;
packable: boolean;
packageMetadata?: PackageMetadata;
};
ConsoleAppConfig
Non-AOT executable:
type ConsoleAppConfig = {
type: "console-app";
selfContained: boolean;
singleFile: boolean;
targetFramework: string;
};
NuGet Integration
Package References
type NuGetPackage = {
name: string;
version: string;
};
// In .csproj
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
Assembly References
For local DLLs (like Tsonic.Runtime):
type AssemblyReference = {
name: string;
hintPath: string;
};
// In .csproj
<Reference Include="Tsonic.Runtime">
<HintPath>../runtime/Tsonic.Runtime.dll</HintPath>
</Reference>
Error Handling
Build errors include:
- dotnet not installed
- Compilation errors
- Missing dependencies
- NativeAOT failures
type BuildResult =
| { ok: true; outputPath: string; buildDir: string }
| { ok: false; error: string; buildDir?: string };