nx
Nx
GitHub
nx-examples
Workspace
GitHub
CI Pipeline Executions
Current execution
Succeeded
master
Runs and Agents
Timeline
Beta
Configuration
Self-Healing CI
Project Graph
Circular Dependencies
Enterprise
Resource Usage
Analysis
Conformance
Enterprise
Runs
Current run
Succeeded
Tasks
Resource Usage
Insights
Compare tasks
Analytics
Sign in
Toggle sidebar
feat(products-api): add a .NET API and generate the Product type from it (#486) * feat(products-api): add a .NET API and generate the Product type from it `apps/products-api` is an ASP.NET Core minimal API serving the same products the storefront already had hardcoded. `Microsoft.Extensions.ApiDescription.Server` writes its OpenAPI document during `dotnet build`, and `shared-product-types:codegen` turns that document into the TypeScript `Product` the Angular apps import. Rename a property on the C# record and the front end stops typechecking. Notes on the wiring, since most of it is not obvious: - The document lands in obj/, which @nx/dotnet already infers as a build output, so no <OpenApiDocumentsDirectory> and no outputs override are needed. The plugin takes no options at all. - `implicitDependencies: ["ProductsApi"]` is what connects the two sides. The generated code is gitignored, so there is no import for Nx to read, and naming the task in dependsOn instead would leave `nx affected` blind: a C# change would mark one project affected and the guard would never run. - codegen hashes the document by exact filename rather than all of obj/, which MSBuild rewrites on every build. - The typecheck default adds the generated sources to consumers' inputs. @nx/js/typescript already covers .d.ts transitively, but this workspace resolves libraries to their sources, so the .ts has to be hashed too. - `postinstall` restores the .NET project, because the inferred build runs --no-restore. - @hey-api/openapi-ts generates the types: two files, no JVM, and it carries the OpenAPI descriptions through as JSDoc. * ci: run the .NET toolchain on the Nx Cloud agents The default `linux-medium-js` agents have no .NET SDK, so ProductsApi:build and the codegen that depends on it failed once Nx Cloud distributed them. `linux-medium-js-dotnet` is that template plus a toolchain: the JS init steps copied from nrwl/nx-cloud-workflows' linux.yaml, the same install-mise step its dotnet template uses, and a `dotnet restore` warm-up. The warm-up is not just for the NuGet cache. The mise shim resolves its config on the first `dotnet` invocation, and doing that during init keeps the probe out of a sandboxed task, where it surfaces as an undeclared read of mise.toml. nrwl/nx has the same step for the same reason. Also starts one Xvfb per agent and points DISPLAY at it. Cypress only spawns its own when DISPLAY is unset, and it always picks :99, so two e2e tasks landing on one agent raced for the display and the loser died with "Display :99 is already in use". `parallelism: false` is not available here, since Nx rejects a non-parallel task that depends on a continuous one. The sandboxing config excludes the reads the Cypress preset's ciWebServerCommand drags in: it nests an `nx run <app>:serve` inside the e2e task, so the serve's dependency chain runs in that task's sandbox. * ci: install the .NET SDK before yarn `postinstall` runs `dotnet restore`, but node/install-packages ran before the SDK install step, so yarn failed with `command not found: dotnet` (exit 127). Move the install above it. The agents were already fine; this is the main CircleCI job only. * refactor: pull codegen in with ^codegen instead of naming the task `^codegen` reaches through projects that have no codegen target of their own, so one targetDefault covers every app instead of each one naming `shared-product-types:codegen`. I previously concluded the opposite. That test was wrong: the app's project.json declared `dependsOn: ["^build"]` with no `"..."`, which replaces the targetDefault rather than inheriting it, so the default never applied. Keeping `"..."` in the project-level list is what makes it work. Verified from a clean slate: build pulls ProductsApi:build then codegen, both e2e suites pass with codegen pulled in through serve, and the rename guard still fires. * ci: use the current agent image in the dotnet launch template nrwl/nx-cloud-workflows retired ubuntu22.04-node20.19-v2 on 2026-08-10 in favour of ubuntu22.04-node24.14-v1, which every upstream template now uses. This template was copied from the older revision, so its agents had no image to boot from and the run ended with zero completed tasks. Claude-Session: https://claude.ai/code/session_01Sr9WDU54dksQvnDxB6csPZ * ci: drop the postinstall restore in favour of the agent init step The hook ran 'dotnet restore' on every 'yarn install', so any environment without a .NET SDK failed at install time rather than when a .NET task ran. Netlify's deploy preview is one of those. The agents already restore in 'Restore .NET projects', so the hook was a second copy of that work. Claude-Session: https://claude.ai/code/session_01Sr9WDU54dksQvnDxB6csPZ * fix(ci): declare mise.toml and tsconfig.json as inputs Task sandboxing flagged two unexpected reads: - ProductsApi:build reads mise.toml, because the dotnet shim resolves the SDK version from it. It belongs in sharedGlobals regardless: a toolchain bump genuinely changes what every task produces. - shared-product-types:codegen reads tsconfig.json. Its inputs array replaced the defaults rather than merging with them, so sharedGlobals never applied. Add it back explicitly. The mise.toml entry in the e2e exclusions is now redundant, since every task declares it through sharedGlobals. Claude-Session: https://claude.ai/code/session_01FMpiAGxMmARaZoonRd7Cov * ci: restore the .NET projects before the deploy step The `deploy` step runs with `--no-agents`, so `ProductsApi:build` executes on the CircleCI container rather than on an Nx Cloud agent. Dropping the postinstall restore left that container with a .NET SDK but no restored packages. Restoring during setup is not enough. The preceding `affected` step pulls that task's cached outputs down from the agent that ran it, and `obj` is one of those outputs, so the agent's `project.assets.json` overwrites the local one and points at NuGet paths that exist only on the agent. The restore has to come after that download, or `dotnet build --no-restore` fails with NETSDK1064. Claude-Session: https://claude.ai/code/session_01FMpiAGxMmARaZoonRd7Cov * refactor(types): configure codegen from package.json Per review on the blog post: for a TypeScript project, a package.json script is the more natural place for this than a project.json target. The command moves to `scripts.codegen` and the graph configuration to the `nx` block, so project.json goes away entirely. Two consequences worth knowing: - An inferred script runs with the project directory as its cwd, so the input path is relative to the library rather than the workspace root, and {projectRoot} no longer applies to it. The `outputs` entry still uses the token, since that is Nx config rather than the script. - Yarn only exposes binaries a workspace declares, so @hey-api/openapi-ts moves from the root to this library. It is the only project that runs it, so this is where it belongs. Claude-Session: https://claude.ai/code/session_01FMpiAGxMmARaZoonRd7Cov * refactor(types): trim the inferable config sourceRoot and projectType were carried over from the deleted project.json, and Nx does not need either of them here. name stays. Without it the project is called after the package, @nx-example/shared-product-types, which is out of step with the short names its siblings use. Claude-Session: https://claude.ai/code/session_01FMpiAGxMmARaZoonRd7Cov * fix(types): hash the generator, exclude yarn's own reads Running codegen as a package.json script puts yarn in the process tree, and yarn reads .yarnrc.yml, yarn.lock and .yarn/install-state.gz before running anything. Those are the package manager checking itself, not inputs to the generator, so they are excluded rather than declared. install-state.gz especially: it is rewritten on every install and differs between machines, so hashing it would never settle. Reading that report turned up a real bug. The task did not hash the library's package.json, so bumping @hey-api/openapi-ts was a cache hit and you would keep the types the old generator produced. Verified both ways: a cache hit before the fix, a re-run after. Claude-Session: https://claude.ai/code/session_01FMpiAGxMmARaZoonRd7Cov * feat(products-api): target .NET 10 The blog post that documents this example already showed 10.0.11, which is what `dotnet add package` resolves today. The example was the thing lagging. mise.toml drives the SDK on the Nx Cloud agents through install-mise, and the CircleCI container installs from its own channel flag, so both move together with the target framework. Package versions came from `dotnet add package` rather than being written by hand, and both resolved to 10.0.11. Claude-Session: https://claude.ai/code/session_01FMpiAGxMmARaZoonRd7Cov
nx-cloud record -- nx format:check --base=0c0f2ccbc2b94e8f137d097fa5fbd6ef7cfa49aa --head=911d83a2392540235252a2c25ef1ff2e46797718
⌘K
Succeeded
nx-cloud record -- nx format:check --base=0c0f2ccbc2b94e8f137d097fa5fbd6ef7cfa49aa --head=911d83a2392540235252a2c25ef1ff2e46797718
Click to copy
Linux
36 CPU cores
911d83a2
master
No resource usage was collected for this run.
GitHub
nx-examples
Workspace