dotnet-shadowrun
v1.1.0
Published
Build a .NET project, copy its output to a temp dir, and run it from there — leaving bin/ unlocked so coding agents/tools can rebuild.
Maintainers
Readme
dotnetrun
Build a .NET project, copy its output to a separate temp directory, and run the
binary from there — so bin/ stays unlocked and coding agents / tools can
keep rebuilding while the app runs.
dotnet run executes the binary straight out of bin/, locking e.g.
TodoApp.exe. That lock blocks the next dotnet build. dotnetrun builds in
place, mirrors the output into a run dir (default D:\temp\<name>-dev on
Windows), and launches from the copy.
Install
npm install -g dotnet-shadowrunOr, for local development of this tool:
# from the repo
npm linkInstalls three equivalent commands — use whichever you like:
dotnetrun,dnrun, ordotnet-shadowrun.
Requires Node.js >= 22.4 and the .NET SDK on PATH.
Usage
dotnetrun --project <path> [options] [-- <app args>]
dotnetrun --project <path> [options] --args <app args>| Option | Default | Description |
| --- | --- | --- |
| -p, --project <path> | — (required) | .csproj file, or a directory containing exactly one |
| -c, --configuration <cfg> | Debug | Debug or Release |
| --temp <path> | <tempRoot>/<name>-dev | Explicit run directory |
| --sync <additive\|mirror> | additive | additive keeps extra files; mirror purges them (robocopy /MIR) |
| --no-build | build on | Skip build; copy + run only |
| --no-run | run on | Build + copy only |
| --detach | attach | Launch and return instead of attaching and streaming logs |
| --launch-profile <name> | first Project profile | Apply a profile from Properties/launchSettings.json (env vars, applicationUrl, commandLineArgs) — same default dotnet run uses |
| --no-launch-profile | | Don't apply any launch profile |
| --env <KEY=VALUE> | | Set an env var on the child (repeatable, highest precedence — overrides the launch profile, same as dotnet run's -e) |
| -h, --help | | Show help |
| -- | | Everything after is forwarded to the executable |
| --args | | Same as --, but safe in PowerShell (see note below) |
PowerShell note: npm's generated
.ps1shim has noparam()block, so args flow through PowerShell's automatic$argsarray — and PowerShell silently drops a bare--token before the child process ever sees it.--works fine in bash/cmd, but if you're on PowerShell (the default on Windows), use--argsinstead so the separator actually survives.
Why
--launch-profileis a dedicated flag, not a forwarded arg:dotnetrunbuilds and spawns the compiled.exedirectly — it never callsdotnet run. Realdotnet run --launch-profile <name>readslaunchSettings.jsonand translates the profile into environment variables before starting the app; the compiled binary itself has no idea what--launch-profilemeans. Passing it as a forwarded app arg (-- --launch-profile X) does nothing. Use--launch-profile <name>as a top-leveldotnetrunoption instead — it reads the sameProperties/launchSettings.jsonand applies the profile'senvironmentVariables(andapplicationUrl→ASPNETCORE_URLS, andcommandLineArgsif you didn't pass your own app args) to the spawned process. It also setsDOTNET_LAUNCH_PROFILEto the profile name, same asdotnet run.Like
dotnet run, if you don't pass--launch-profileat all,dotnetrunstill auto-applies the first profile whosecommandNameis"Project"inlaunchSettings.json— pass--no-launch-profileto opt out entirely. (workingDirectoryin a profile is intentionally not honored — realdotnet rundoesn't support it either; dotnet/sdk#20885 closed it "not planned".)
Logs stream to the console by default. In attached mode, Ctrl+C stops the app.
Default run directory
- Windows:
D:\temp\<name>-dev(falls back to the system temp dir ifD:\is absent) - macOS / Linux:
<system temp>/<name>-dev
<name> is the project's AssemblyName lowercased — so TodoApp.UI →
todoapp → …\todoapp-dev. Override the whole path with --temp.
Examples
# Build Debug, copy, attach and stream logs
dotnetrun --project ./TodoApp.UI
# Release, launch detached
dotnetrun -p ./TodoApp.UI -c Release --detach
# Forward args to the app (e.g. enable Langfuse tracing)
dotnetrun -p ./TodoApp.UI -- --enable-langfuse
# Same, but PowerShell-safe (bash/cmd's -- gets swallowed by PowerShell)
dotnetrun -p ./TodoApp.UI --args --enable-langfuse
# Re-copy and run without rebuilding
dotnetrun -p ./TodoApp.UI --no-build
# Clean room: mirror output, purging stale DLLs
dotnetrun -p ./TodoApp.UI --sync mirror
# Apply a specific launch profile (env vars, applicationUrl, commandLineArgs)
dotnetrun -p ./TodoApp.UI --launch-profile Staging
# Skip launch profile application (dotnetrun otherwise auto-applies the
# first "Project" profile, same default dotnet run uses)
dotnetrun -p ./TodoApp.UI --no-launch-profile
# Override/add an env var on top of the launch profile (highest precedence)
dotnetrun -p ./TodoApp.UI --env ASPNETCORE_ENVIRONMENT=StagingHow it works
- Resolve —
dotnet msbuild -getProperty:AssemblyName,TargetDir,OutputTypefinds the real assembly name and output dir (no build needed). Falls back to scanningbin/<config>for the newest executable. - Build —
dotnet build -c <config>(skip with--no-build). - Copy — Windows
robocopy /MT(/Eadditive or/MIRmirror); macOS/Linuxrsync -a [--delete], falling back to a recursive copy. - Run — spawn the copied executable with the run dir as its working
directory, forwarding any
--args.
Notes / limitations
- If a previous instance is still running and holding files in the run dir, the copy can fail with a lock error (robocopy exit ≥ 8). Close it first.
- Multi-targeted projects: MSBuild reports the first
TargetFramework's output. - v1 does not inject environment variables or run
dotnet publishpackaging.
