diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md deleted file mode 100644 index f969e2ef..00000000 --- a/.github/copilot-instructions.md +++ /dev/null @@ -1,145 +0,0 @@ -# Copilot Instructions - -Guidance for AI agents working in **Atom** — an opinionated, type-safe build automation framework -for .NET. Build logic is defined in C# (debuggable like normal code), and Atom generates the CI/CD -YAML for GitHub Actions and Azure DevOps. Keep changes focused and defer to the linked docs for -detail. - -## What's in the repo - -| Project | Role | -|---------|------| -| `src/Invex.Atom.Build` | The core framework: build definitions, targets, params/secrets, artifacts, hosting, lifecycle hooks | -| `src/Invex.Atom.Build.Analyzers` | Roslyn analyzers shipped with the framework (e.g. `[PublicAPI]` enforcement) | -| `src/Invex.Atom.Build.SourceGenerators` | Source generators that discover targets/params and generate the entry point (`netstandard2.0`) | -| `src/Invex.Atom.Workflows` | Workflow definitions, triggers, and options on top of a base build | -| `src/Invex.Atom.Module.GithubWorkflows` | GitHub Actions YAML generation module | -| `src/Invex.Atom.Module.DevopsWorkflows` | Azure DevOps pipeline YAML generation module | -| `src/Invex.Atom.Module.Dotnet` | .NET build/test/pack targets module | -| `src/Invex.Atom.Module.GitVersion` | GitVersion-based build ID/version providers | -| `src/Invex.Atom.Module.AzureKeyVault` / `AzureStorage` | Azure secrets / artifact providers | -| `src/Invex.Atom.Tool` | The `atom` dotnet global tool (`Invex.Atom.Tool`) | -| `src/Invex.Atom.DotnetCliGenerator` | Generates the dotnet CLI model | -| `tests/*` | NUnit test suites, plus `Invex.Atom.TestUtils` (published test helpers) | -| `_atom/` | This repo's own Atom build definition (`IBuild.cs`) — Atom is dogfooded to build itself | -| `samples/` | Sample projects (Hello World, params, analyzer samples) | - -The DocFX documentation site is configured by `docfx.json` with content in `docs/`, `api/`, -`index.md`, and `toc.yml`. - -## Build & language specifics - -- **.NET 10 SDK** is required. Library and test projects multi-target `net8.0;net9.0;net10.0`; - `_atom` targets `net10.0`; source generators/analyzers target `netstandard2.0`. -- The solution file is `Invex.Atom.slnx` (slnx format). -- C# `LangVersion` 14, `ImplicitUsings` and `Nullable` enabled, `TreatWarningsAsErrors` on. -- Global usings live in each project's `_usings.cs` — add shared usings there, not per-file. -- `GenerateDocumentationFile` is on and `CS1591` is **enforced** in `src/` — every public type - and member needs XML doc comments. (`_atom` and the test projects suppress `CS1591` locally; do - not re-add it to the repo-wide `NoWarn` in `Directory.Build.props`.) - -Build and test the whole solution: - -```shell -dotnet build Invex.Atom.slnx -dotnet test Invex.Atom.slnx -``` - -Or use the repo's own Atom targets (run from the repo root): - -```shell -atom PackProjects # pack all NuGet packages -atom TestProjects # run the test suites -atom BuildDocs # build the DocFX site (atom ServeDocs to preview) -``` - -(`atom` is the `Invex.Atom.Tool` global tool; `dotnet run --project _atom -- ` is -equivalent.) - -## Architecture overview - -- A build is an `internal interface IBuild` annotated `[BuildDefinition]` and - `[GenerateEntryPoint]`, extending `IBuildDefinition` (or `IWorkflowBuildDefinition` plus module - interfaces). Source generators discover targets/params and generate the program entry point. -- **Targets** are `Target` properties built with a fluent API: `.DescribedAs(...)`, - `.DependsOn(...)`, `.RequiresParam(...)` / `.UsesParam(...)`, `.ProducesArtifact(...)` / - `.ConsumesArtifact(...)`, `.ConsumesVariable(...)`, `.Executes(...)`. -- **Params/secrets** are declared with `[ParamDefinition]` / `[SecretDefinition]` and resolved via - `GetParam(() => Prop)`. Secrets are masked in logs. -- **Providers** are the extensibility points: `ISecretsProvider`, `IArtifactProvider`, - `IVariableProvider`, `IBuildIdProvider`, `IBuildVersionProvider` (`SemVer Version`), - `IBuildTimestampProvider`, `IPathProvider`, `IOutcomeReportWriter`. Modules plug in by - registering these (see `docs/developer-guide/custom-providers.md` and `writing-a-module.md`). -- **Workflows** (`IWorkflowBuildDefinition.Workflows`) declare `WorkflowDefinition`s — triggers, - targets, matrix dimensions, options, and types (`WorkflowTypes.Github.Action`, - `WorkflowTypes.Devops.Pipeline`) — from which the CI/CD YAML is generated. - -## This repo's workflows are generated - -The YAML under `.github/workflows/` (`Validate.yml`, `Build.yml`, -`Dependabot Enable auto-merge.yml`), `.github/dependabot.yml`, and the DevOps test pipeline -(`.devops/workflows/Test_Devops_Build.yml`) are all **generated** from `_atom/IBuild.cs`. - -Whenever you change anything that affects the workflows — targets, workflow definitions, triggers, -options, or params/secrets in `_atom/` — regenerate the YAML: - -```shell -atom gen -``` - -Commit the regenerated files alongside your `_atom/` changes; never hand-edit the generated YAML. -A drift between `_atom/IBuild.cs` and the committed YAML should be treated as a missing -`atom gen` run. - -## Conventions - -- Annotate every new public type with `[PublicAPI]` — the in-repo analyzer flags anything missing, - and warnings are errors. -- Add XML doc comments to all public types and members in `src/`. Match the existing `` / - `` / `` / `` style, and keep docs **accurate to the implementation**. -- Use Conventional Commits — the prefix drives versioning (see `GitVersion.yml`): - - | Prefix | Version bump | - |--------|--------------| - | `breaking:` / `major:` | Major | - | `feat:` / `feature:` / `minor:` | Minor | - | `fix:` / `patch:` | Patch | - | `semver-none` / `semver-skip` | No bump | - -- When adding user-facing features, update the relevant `docs/` page and `README.md`. The README - is packed into the NuGet packages — keep links absolute where they must work outside the repo. - -## Testing & the Verify workflow - -- Tests use **NUnit** with **Shouldly**, **FakeItEasy**, and **Verify** (`Verify.NUnit`) for - snapshot/approval testing. `Invex.Atom.TestUtils` provides shared helpers (and is itself a - published package). -- Workflow-generation tests snapshot the generated YAML into `*.verified.txt` files. A snapshot - test fails when its output differs; Verify writes a `*.received.txt` next to it. -- If the diff is unintended, fix the code. If the change is valid (expected new output), accept - it and re-run: - 1. Overwrite the `*.verified.txt` with the contents of the matching `*.received.txt`. - 2. Delete the `*.received.txt`. - 3. Re-run `dotnet test` to confirm the suite is green. -- The Validate workflow's `CheckPrForBreakingChanges` target diffs **all** - `tests/**/*.verified.txt` files against the latest release and requires a matching major/minor - version bump for breaking changes. Snapshot changes must therefore be intentional, committed, - and paired with an appropriate Conventional Commit prefix. - -## Defer to the docs - -For anything beyond the above, prefer these over duplicating detail: - -- `README.md` — overview, quick-start examples, and the full docs index. -- `docs/getting-started/` — introduction, your first build, base vs workflow builds. -- `docs/core-concepts/` — build definitions, targets, parameters, secrets, artifacts, variables, - file system, process runner, build info, build options, hosting, lifecycle hooks, - logging & reports, file transformations. -- `docs/workflows/` — workflow definitions, triggers, options, variables, debugging. -- `docs/modules/` — the built-in modules (Dotnet, GithubWorkflows, DevopsWorkflows, - AzureKeyVault, AzureStorage, GitVersion). -- `docs/built-in-targets/` — `SetupBuildInfo`, `ValidateBuild`, `GenerateWorkflowFiles`. -- `docs/developer-guide/` — writing a module, custom providers, source generators, testing. -- `docs/reference/cli.md` — the `atom` CLI and its arguments. - - diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 00000000..eb60d58f --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,129 @@ +# Agent Instructions + +Guidance for AI agents working in **Atom**, an opinionated, type-safe build automation framework +for .NET. Keep changes focused; `README.md` and `docs/` describe consumer-facing usage. + +## Repository map + +| Area | Purpose | +|------|---------| +| `src/Invex.Atom.Build` | Core build definitions, targets, parameters, secrets, artifacts, hosting, and lifecycle hooks | +| `src/Invex.Atom.Workflows` | Workflow definitions, triggers, and options | +| `src/Invex.Atom.Module.*` | Built-in modules for .NET, GitHub Actions, Azure DevOps, GitVersion, and Azure services | +| `src/Invex.Atom.Build.Analyzers` / `SourceGenerators` | Roslyn analyzers and generated build entry points | +| `src/Invex.Atom.Tool` | The `Invex.Atom.Tool` global tool | +| `tests/` | NUnit tests, snapshots, and the published `Invex.Atom.TestUtils` helpers | +| `_atom/` | This repository's own Atom build definition | +| `samples/` | Small consumer examples | +| `docs/`, `api/` | DocFX documentation source and generated API content | + +The solution is `Invex.Atom.slnx`. The repository currently requires the .NET 10 SDK; libraries +and tests target `net8.0`, `net9.0`, and `net10.0`, while the build project targets `net10.0`. +There is no `global.json`, so use the installed .NET 10 SDK selected by the environment. + +## Build, test, and cleanup + +Run commands from the repository root: + +```shell +dotnet build Invex.Atom.slnx +dotnet test Invex.Atom.slnx +``` + +The repository's own build targets can also be used: + +```shell +atom PackProjects +atom TestProjects +atom BuildDocs +atom ServeDocs +``` + +`atom` is the `Invex.Atom.Tool` global tool. The equivalent form is +`dotnet run --project _atom -- `. + +After making C# changes, run ReSharper cleanup over the solution: + +```powershell +$sdk = dotnet --version +jb cleanupcode Invex.Atom.slnx --include="**.cs" --toolset-path="C:\Program Files\dotnet\sdk\$sdk\MSBuild.dll" +``` + +If `jb` is unavailable, install it with +`dotnet tool install --global JetBrains.ReSharper.GlobalTools`. The explicit `--toolset-path` +ensures cleanup uses the selected .NET SDK's MSBuild rather than an incompatible Visual Studio +BuildTools installation. Cleanup honors `.editorconfig` and `Invex.Atom.sln.DotSettings`. + +## Build and language conventions + +- `ImplicitUsings`, nullable reference types, documentation generation, and + `TreatWarningsAsErrors` are enabled in `Directory.Build.props`. +- Global usings belong in each project's `_usings.cs`, not in individual source files. +- Add XML documentation to every new public type and member in `src/`, matching the existing + style and accurately describing nullability, return values, retries, and caching. +- Add `[PublicAPI]` to new public types where the public API analyzer applies. +- Some generated-code projects and tests explicitly suppress `CS1591`; do not broaden the + repository-wide warning suppressions to hide missing documentation. +- Keep cross-member implementation helpers `internal`; expose only the intended consumer API. +- The IDE can report false errors for `[GeneratedRegex]` partial members; `dotnet build` is the + source of truth for those members. + +## Atom architecture + +- A build is an `internal interface IBuild` annotated with `[BuildDefinition]` and + `[GenerateEntryPoint]`, extending `IBuildDefinition` or + `IWorkflowBuildDefinition` plus module interfaces. +- Targets use the fluent API for descriptions, dependencies, parameters, artifacts, variables, + and execution. +- Parameters and secrets use `[ParamDefinition]` / `[SecretDefinition]` and + `GetParam(() => Property)`. Secrets are masked in logs. +- Providers are the extension points for secrets, artifacts, variables, build identity/version, + timestamps, paths, and outcome reports. See `docs/developer-guide/`. +- Workflow definitions describe triggers, targets, matrices, options, and GitHub Actions or + Azure DevOps output types. + +## Generated workflows + +The committed workflow files under `.github/workflows/`, `.github/dependabot.yml`, and +`.devops/workflows/` are generated from `_atom/IBuild.cs`. When changing targets, workflow +definitions, triggers, options, or parameters/secrets that affect them, run: + +```shell +atom gen +``` + +Commit the generated files with the source change. Never hand-edit generated workflow files. + +## Versioning and commits + +Use Conventional Commits; `GitVersion.yml` maps prefixes as follows: + +| Prefix | Version bump | +|--------|--------------| +| `breaking:` / `major:` | Major | +| `feat:` / `feature:` / `minor:` | Minor | +| `fix:` / `patch:` | Patch | +| `semver-none` / `semver-skip` | No bump | + +## Testing and Verify snapshots + +Tests use NUnit, Shouldly, FakeItEasy, and Verify (`Verify.NUnit`). Workflow and source-generator +tests compare output with committed `*.verified.txt` files. A mismatch creates a corresponding +`*.received.txt` file: + +1. Fix the implementation if the output is unintended. +2. If the output is intentional, replace the matching `.verified.txt` with the received output, + remove `.received.txt`, and rerun `dotnet test`. +3. Keep approved snapshot changes intentional because PR validation checks the snapshots for + breaking API changes. + +## Change checklist + +1. Follow existing patterns and make a precise, surgical change. +2. Add public API documentation and `[PublicAPI]` where applicable. +3. Build and test the solution. +4. Run `jb cleanupcode` and include its relevant formatting changes. +5. Update `README.md` or the relevant documentation for consumer-facing behavior. +6. Run `atom gen` whenever generated workflows are affected. + +Do not commit secrets, generated planning notes, or unrelated formatting changes. diff --git a/README.md b/README.md index 98237823..394166fa 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,17 @@ Atom is an opinionated, type-safe build automation framework for .NET. It enables you to define your build logic in C#, debug it like standard code, and automatically generate CI/CD configuration files for GitHub Actions and Azure DevOps. +## Requirements + +- .NET 10 SDK +- A .NET project containing an Atom build definition (commonly `_atom/_atom.csproj`) + +Install the CLI once if you want to invoke builds from any directory: + +```shell +dotnet tool install --global Invex.Atom.Tool +``` + ## Why Atom? ### Zero Context Switching @@ -35,15 +46,15 @@ Reduces boilerplate by automatically discovering targets and parameters. > [!NOTE] > -> It is recommended to use the atom dotnet tool to invoke atom projects: +> It is recommended to use the `atom` dotnet tool to invoke Atom projects: > -> `dotnet tool install -g Invex.Atom.Tool` +> `dotnet tool install --global Invex.Atom.Tool` > -> ` atom ...` +> `atom ...` > > However, the dotnet cli can also be used directly: > -> `dotnet run -- ...` +> `dotnet run --project _atom -- ...` 1. Create a .NET 10 project diff --git a/docfx-theme/public/main.css b/docfx-theme/public/main.css new file mode 100644 index 00000000..64260593 --- /dev/null +++ b/docfx-theme/public/main.css @@ -0,0 +1,6 @@ +#logo { + width: 32px !important; + height: 32px !important; + object-fit: contain !important; + margin-right: 8px !important; +} diff --git a/docfx.json b/docfx.json index 95cc8879..24a33775 100644 --- a/docfx.json +++ b/docfx.json @@ -34,6 +34,7 @@ }, { "files": [ + "api/toc.yml", "api/**.yml", "api/index.md" ] @@ -48,7 +49,8 @@ "resource": [ { "files": [ - "images/**" + "images/**", + "LICENSE.txt" ] } ], @@ -58,6 +60,7 @@ "globalMetadata": { "_appTitle": "Atom", "_appName": "Atom", + "_appLogoPath": "images/icon.png", "_appFooter": "Atom — Build Automation for .NET", "_enableSearch": true, "_disableContribution": false, @@ -71,10 +74,11 @@ "default", "modern" ], + "theme": [ + "docfx-theme" + ], "postProcessors": [], "keepFileLink": false, "disableGitFeatures": false } } - - diff --git a/docs/built-in-targets/generate-workflow-files.md b/docs/built-in-targets/generate-workflow-files.md index cb9eeb79..dae14e19 100644 --- a/docs/built-in-targets/generate-workflow-files.md +++ b/docs/built-in-targets/generate-workflow-files.md @@ -31,8 +31,9 @@ atom Gen Your build must: -1. Inherit from `WorkflowBuildDefinition` (or implement `IWorkflowBuildDefinition`). -2. Override the `Workflows` property with at least one definition. +1. Implement `IWorkflowBuildDefinition` (or derive from `WorkflowBuildDefinition`). +2. Define `IWorkflowBuildDefinition.Workflows` with at least one definition. Interface builds + explicitly implement the property; partial classes override it. 3. Inherit a platform module interface (`IGithubWorkflows`, `IDevopsWorkflows`, or both) so the corresponding workflow writer is registered. diff --git a/docs/core-concepts/build-definitions.md b/docs/core-concepts/build-definitions.md index 0a1410f8..3d4d7df9 100644 --- a/docs/core-concepts/build-definitions.md +++ b/docs/core-concepts/build-definitions.md @@ -1,25 +1,48 @@ # Build Definitions -A **build definition** is the central class of an Atom build. It declares which targets exist, what parameters are -available, and how the build host is configured. +A **build definition** is the central entry point for an Atom build. It declares which targets exist, what parameters +are available, and how the build host is configured. For consumer builds, the recommended approach is an interface +that extends `IBuildDefinition`; use a partial class when the build needs class-specific host configuration. -## The `[BuildDefinition]` Attribute +## Recommended: interface-based builds -Every build definition class must be: +Define the build as an interface annotated with `[BuildDefinition]` and `[GenerateEntryPoint]`: -1. Decorated with `[BuildDefinition]` -2. Marked `partial` (so source generators can augment it) -3. Derived from `BuildDefinition` (or `WorkflowBuildDefinition`) +```csharp +[BuildDefinition] +[GenerateEntryPoint] +internal interface IBuild : IBuildDefinition +{ + Target SayHello => t => t + .DescribedAs("Prints a greeting") + .Executes(() => Logger.LogInformation("Hello, World!")); +} +``` + +This pattern is recommended because the build can compose targets, parameters, and host +configuration from module interfaces without putting all implementation in one type. The generated +entry point discovers the interface members and runs the build. + +## Alternative: partial class builds + +Use a partial class when the build itself needs to override virtual members such as +`ConfigureDefinitionHost`: ```csharp [BuildDefinition] [GenerateEntryPoint] internal partial class Build : BuildDefinition { - // targets, parameters, etc. + Target SayHello => t => t + .DescribedAs("Prints a greeting") + .Executes(() => Logger.LogInformation("Hello, World!")); } ``` +The class must be `partial` so source generators can augment it, and it must derive from +`BuildDefinition` or `WorkflowBuildDefinition`. It can also implement module interfaces in the +same way as an interface-based build. + ## What the Source Generator Does When you apply `[BuildDefinition]`, the Atom source generator automatically: @@ -49,7 +72,7 @@ var builder = AtomHost.CreateAtomBuilder(args); builder.Build().UseAtom().Run(); ``` -## Composing with Interfaces +## Composing module interfaces Targets and parameters are typically defined in **interfaces** so they can be shared across builds or published in module packages: @@ -92,4 +115,3 @@ public override void ConfigureDefinitionHost(IHostApplicationBuilder builder) ## Next Steps → [Targets](targets.md) - diff --git a/docs/developer-guide/source-generators.md b/docs/developer-guide/source-generators.md index f39caa4c..5d2cbc18 100644 --- a/docs/developer-guide/source-generators.md +++ b/docs/developer-guide/source-generators.md @@ -16,10 +16,11 @@ Both are automatically referenced when you add `Invex.Atom.Build`. ### `[BuildDefinition]` -For a class decorated with `[BuildDefinition]`, the generator emits: +For a build definition decorated with `[BuildDefinition]` (either an interface extending +`IBuildDefinition` or a partial class deriving from `BuildDefinition`), the generator emits: -- `TargetDefinitions` — a dictionary mapping target names to `Target` delegates, collected from the class and all - implemented interfaces. +- `TargetDefinitions` — a dictionary mapping target names to `Target` delegates, collected from the build definition and + all implemented interfaces. - `ParamDefinitions` — a dictionary mapping parameter names to `ParamDefinition` records, collected from `[ParamDefinition]` / `[SecretDefinition]` attributes. - `AccessParam` — a method that can read any declared parameter by name. @@ -70,4 +71,3 @@ To inspect the generated code: ## Next Steps → [Testing](testing.md) - diff --git a/docs/developer-guide/testing.md b/docs/developer-guide/testing.md index a7ee7942..1099e3ab 100644 --- a/docs/developer-guide/testing.md +++ b/docs/developer-guide/testing.md @@ -20,16 +20,44 @@ dotnet add package Invex.Atom.TestUtils - Testing parameter resolution - Validating generated workflow models -## Testing a Target +## Testing a Build Model -Set up a test build with mocked services, execute a target, and assert the results: +`CreateTestHost` creates an Atom host with test providers and an in-memory file system. This +example verifies that a target was discovered: ```csharp -// Arrange - create a test host with your build definition -// Act - execute the target -// Assert - verify the expected behaviour +using Invex.Atom.Build.Definition; +using Invex.Atom.Build.Hosting; +using Invex.Atom.Build.Model; +using Invex.Atom.TestUtils; +using Microsoft.Extensions.DependencyInjection; +using NUnit.Framework; +using Shouldly; + +[BuildDefinition] +internal sealed partial class TestBuild : BuildDefinition +{ + public Target SayHello => t => t + .Executes(() => Logger.LogInformation("Hello, World!")); +} + +public sealed class BuildTests +{ + [Test] + public void Build_discovers_target() + { + using var host = TestUtils.CreateTestHost(); + + var model = host.Services.GetRequiredService(); + + model.Targets.ShouldContainKey(nameof(TestBuild.SayHello)); + } +} ``` +For target execution tests, retrieve the registered `BuildExecutor`, provide the desired +`CommandLineArgs`, and assert on the resulting target state or captured `TestConsole` output. + ## Testing Workflow Generation Verify that your workflow definitions produce the expected YAML by: @@ -43,4 +71,3 @@ Verify that your workflow definitions produce the expected YAML by: - Use `System.IO.Abstractions.TestingHelpers` (already a dependency) for in-memory file system testing. - Mock `IProcessRunner` to avoid executing real processes in tests. - Use snapshot testing to verify generated YAML doesn't change unexpectedly. - diff --git a/docs/developer-guide/writing-a-module.md b/docs/developer-guide/writing-a-module.md index d704ef80..2a4db6e9 100644 --- a/docs/developer-guide/writing-a-module.md +++ b/docs/developer-guide/writing-a-module.md @@ -22,7 +22,7 @@ A module is a NuGet package that provides one or more of: 2. Add a reference to `Invex.Atom.Build` (and `Invex.Atom.Workflows` if your module contributes workflow features): ```xml - + ``` 3. Create a `.props` file (optional but recommended) to auto-import usings when consumers reference your package. @@ -110,4 +110,3 @@ The consumer just implements the interface — all targets, parameters, and serv ## Next Steps → [Custom Providers](custom-providers.md) - diff --git a/docs/filterConfig.yml b/docs/filterConfig.yml index 55bfb8e7..fabcc984 100644 --- a/docs/filterConfig.yml +++ b/docs/filterConfig.yml @@ -6,7 +6,6 @@ apiRules: - exclude: uidRegex: ^.*\.Internal\..*$ - include: - uidRegex: ^DecSm\. + uidRegex: ^Invex\. - exclude: - uidRegex: ^(?!DecSm\.) - + uidRegex: ^(?!Invex\.) diff --git a/docs/getting-started/base-vs-workflow-build.md b/docs/getting-started/base-vs-workflow-build.md index 722a45d7..d93ce225 100644 --- a/docs/getting-started/base-vs-workflow-build.md +++ b/docs/getting-started/base-vs-workflow-build.md @@ -1,11 +1,25 @@ # Base Build vs Workflow Build -Atom offers two base classes for your build definition. Which one you choose depends on whether you need to generate -CI/CD pipeline files. +Atom supports interface and partial-class build definitions. Prefer the interface pathway for consumer +builds; choose the partial-class alternative when you need class-specific host configuration. In +either pathway, choose the base contract according to whether you need to generate CI/CD files. -## `BuildDefinition` — The Base Build +## Basic builds -Use `BuildDefinition` when you only need to run builds locally (or you manage your CI YAML by hand). +Use `IBuildDefinition` when you only need to run builds locally (or you manage your CI YAML by hand): + +```csharp +[BuildDefinition] +[GenerateEntryPoint] +internal interface IBuild : IBuildDefinition +{ + Target Compile => t => t + .DescribedAs("Compiles the solution") + .Executes(() => { /* ... */ }); +} +``` + +The partial-class equivalent is: ```csharp [BuildDefinition] @@ -18,7 +32,7 @@ internal partial class Build : BuildDefinition } ``` -`BuildDefinition` gives you: +Both forms provide: - Target discovery and execution with dependency resolution - Parameter and secret management @@ -26,26 +40,26 @@ internal partial class Build : BuildDefinition - Process runner, file system, logging, reports - All core concepts documented in this guide -## `WorkflowBuildDefinition` — Adding CI/CD Generation +## Workflow builds -`WorkflowBuildDefinition` extends `BuildDefinition` with the ability to define **workflows** — descriptions of how your -targets map to CI/CD jobs — and generate the corresponding YAML files. +Use `IWorkflowBuildDefinition` when the build also defines CI/CD workflows. Add a platform module +interface such as `IGithubWorkflows` or `IDevopsWorkflows` to register the corresponding writer: ```csharp [BuildDefinition] [GenerateEntryPoint] -internal partial class Build : WorkflowBuildDefinition, IGithubWorkflows +internal interface IBuild : IWorkflowBuildDefinition, IGithubWorkflows { - private Target Compile => t => t + Target Compile => t => t .DescribedAs("Compiles the solution") .Executes(() => { /* ... */ }); - private Target Test => t => t + Target Test => t => t .DescribedAs("Runs tests") - .DependsOn(Compile) + .DependsOn(nameof(Compile)) .Executes(() => { /* ... */ }); - public override IReadOnlyList Workflows => + IReadOnlyList IWorkflowBuildDefinition.Workflows => [ new("CI") { @@ -61,10 +75,13 @@ internal partial class Build : WorkflowBuildDefinition, IGithubWorkflows } ``` -Running `dotnet run -- Gen` writes a GitHub Actions YAML file that calls your build with the correct targets. -Inheriting `IGithubWorkflows` (from `Invex.Atom.Module.GithubWorkflows`) registers the GitHub Actions workflow writer. +The partial-class equivalent derives from `WorkflowBuildDefinition` instead: +`internal partial class Build : WorkflowBuildDefinition, IGithubWorkflows`. + +Running `dotnet run -- Gen` writes a GitHub Actions YAML file that calls the build with the correct targets. +`IGithubWorkflows` comes from `Invex.Atom.Module.GithubWorkflows`. -### What `WorkflowBuildDefinition` adds +### What workflow builds add | Feature | Description | |----------------------|-----------------------------------------------------------------------------------------------------------| @@ -77,12 +94,11 @@ Inheriting `IGithubWorkflows` (from `Invex.Atom.Module.GithubWorkflows`) registe ### When to upgrade -You can always start with `BuildDefinition` and switch to `WorkflowBuildDefinition` later — the change is additive. Your -existing targets, parameters, and modules continue to work unchanged; you just gain the `Workflows` property and the -`Gen` target. +You can start with `IBuildDefinition` and later change it to `IWorkflowBuildDefinition`; existing +targets, parameters, and module interfaces can remain unchanged while you add the `Workflows` +property and `Gen` target. ## Next Steps → [Build Definitions](../core-concepts/build-definitions.md) — deep dive into the `[BuildDefinition]` attribute and source generators - diff --git a/docs/getting-started/introduction.md b/docs/getting-started/introduction.md index 9e407e49..7bb11291 100644 --- a/docs/getting-started/introduction.md +++ b/docs/getting-started/introduction.md @@ -8,7 +8,7 @@ full IDE support — IntelliSense, refactoring, and step-through debugging. | Concept | Description | |----------------------|------------------------------------------------------------------------------------------------------------------------| -| **Build Definition** | A C# class that declares your targets, parameters, and build configuration. | +| **Build Definition** | A C# interface (recommended) or partial class that declares your targets, parameters, and build configuration. | | **Target** | A named unit of work (compile, test, pack, deploy, etc.) with optional dependencies on other targets. | | **Parameter** | A value that can be supplied via the command line, environment variable, `appsettings.json`, or a secrets provider. | | **Module** | A NuGet package that adds reusable targets, parameters, or service registrations to your build. | @@ -41,9 +41,10 @@ through Atom (for example via `IBuildAccessor.RootedFileSystem` and `IBuildAcces ## How It Works 1. You create a C# project (or a single `.cs` file) that references the Atom packages. -2. You define a class decorated with `[BuildDefinition]` that inherits from `BuildDefinition` (or - `WorkflowBuildDefinition` if you need CI/CD generation). -3. Inside that class you declare **targets** — lambda-based definitions that describe what to execute, their +2. You define an interface decorated with `[BuildDefinition]` that extends `IBuildDefinition` (or + `IWorkflowBuildDefinition` if you need CI/CD generation). A partial class deriving from + `BuildDefinition` or `WorkflowBuildDefinition` is the alternative when class-specific overrides are needed. +3. Inside that build definition you declare **targets** — lambda-based definitions that describe what to execute, their dependencies, required parameters, and produced artifacts. 4. You run the build with `dotnet run -- ` (or via the `atom` global tool). 5. If you use `WorkflowBuildDefinition`, running the `Gen` target emits platform-specific YAML that @@ -52,4 +53,3 @@ through Atom (for example via `IBuildAccessor.RootedFileSystem` and `IBuildAcces ## Next Steps → [Your First Build](your-first-build.md) - diff --git a/docs/getting-started/your-first-build.md b/docs/getting-started/your-first-build.md index b5518195..dfabc864 100644 --- a/docs/getting-started/your-first-build.md +++ b/docs/getting-started/your-first-build.md @@ -4,18 +4,18 @@ This guide walks you through creating a minimal Atom build and running it locall ## Prerequisites -- [.NET 8 SDK](https://dotnet.microsoft.com/download) or later +- [.NET 10 SDK](https://dotnet.microsoft.com/download) ## Option 1 — Single-File Build (Simplest) Create a file called `Build.cs` anywhere on disk: ```csharp -#:package Invex.Atom@2.* +#:package Invex.Atom.Build@3.* [BuildDefinition] [GenerateEntryPoint] -partial class Build : BuildDefinition +internal interface IBuild : IBuildDefinition { Target SayHello => t => t .DescribedAs("Prints a hello world message") @@ -29,8 +29,9 @@ Run it: dotnet run Build.cs SayHello ``` -That's it. The `#:package` directive pulls in the Atom NuGet package automatically, `[GenerateEntryPoint]` -source-generates a `Main` method, and the `SayHello` target is discovered and executed. +That's it. The `#:package` directive pulls in `Invex.Atom.Build`, `[GenerateEntryPoint]` source-generates +a `Main` method, and the `SayHello` target is discovered and executed. Interface-based builds are +the recommended pattern because they compose naturally with module interfaces. ## Option 2 — Project-Based Build @@ -46,23 +47,20 @@ For larger builds you'll typically use a dedicated project. ```shell cd _atom - dotnet add package Invex.Atom + dotnet add package Invex.Atom.Build ``` -3. Replace `Program.cs` with a build definition (or use `[GenerateEntryPoint]` to have the entry point generated for - you). Here's the minimal version with `[GenerateEntryPoint]`: +3. Replace `Program.cs` with the recommended interface-based build definition. `[GenerateEntryPoint]` + generates the entry point for you: ```csharp - using Invex.Atom.Build.Definition; - using Invex.Atom.Build.Hosting; - namespace Atom; [BuildDefinition] [GenerateEntryPoint] - internal partial class Build : BuildDefinition + internal interface IBuild : IBuildDefinition { - private Target HelloWorld => t => t + Target HelloWorld => t => t .DescribedAs("Prints a hello world message") .Executes(() => { @@ -71,6 +69,9 @@ For larger builds you'll typically use a dedicated project. } ``` + If the build needs to override class members such as `ConfigureDefinitionHost`, use an + `internal partial class Build : BuildDefinition` instead. + 4. Run the build: ```shell @@ -139,4 +140,3 @@ Parameters can also be supplied via `appsettings.json`: ## Next Steps → [Base vs Workflow Build](base-vs-workflow-build.md) — understand when you need workflow support - diff --git a/docs/reference/cli.md b/docs/reference/cli.md index e1920ba2..f554972e 100644 --- a/docs/reference/cli.md +++ b/docs/reference/cli.md @@ -1,13 +1,22 @@ # CLI Reference -Atom builds are invoked via `dotnet run` or the `atom` global tool. +Atom builds are invoked via `dotnet run` or the `atom` global tool. The repository currently +requires the .NET 10 SDK. Libraries may target `net8.0`, `net9.0`, and `net10.0`. ## Invocation -### Via `dotnet run` +### Via `dotnet run` (project-based) ```shell -dotnet run -- [options] [--param-name value ...] +dotnet run --project _atom -- [options] [--param-name value ...] +``` + +For a project located in the current directory, `dotnet run -- ...` is also sufficient. + +To run a file-based build: + +```shell +dotnet run Build.cs [options] [--param-name value ...] ``` ### Via the `atom` Global Tool @@ -24,7 +33,8 @@ Then run: atom [options] [--param-name value ...] ``` -The tool discovers your Atom project (defaults to the `_atom` directory) and runs it. +The tool searches the current directory and its parents for `_atom`, `_build`, `Atom`, or `Build` +projects/files. On case-sensitive systems it also checks lowercase `atom` and `build`. ## Targets @@ -45,17 +55,21 @@ Targets execute in dependency order. Duplicates are resolved automatically. | `--headless` | `-hl` | Non-interactive mode (no prompts, plain output) | | `--verbose` | `-v` | Enable verbose (debug-level) logging | | `--interactive` | `-i` | Prompt for missing required parameters | -| `--project ` | `-p ` | Specify the Atom project directory (default: `_atom`) | +| `--project ` | `-p ` | Specify the Atom project or project name | +| `--file ` | `-f ` | Specify a file-based C# build | +| `--no-restore-cache` | | Force restore and build instead of using the Atom caches | ## Parameters Pass parameter values with `-- `: ```shell -dotnet run -- Deploy --api-key sk-123 --environment production +dotnet run --project _atom -- Deploy --api-key example-key --environment production ``` -Parameter names use kebab-case on the command line and are matched to `[ParamDefinition]` attributes. +Parameter names use kebab-case on the command line and are matched to `[ParamDefinition]` +attributes. Avoid passing real secrets on the command line because shell history and process +diagnostics may expose them; use a secret provider, user secrets, or CI secret injection instead. ## Examples @@ -66,8 +80,8 @@ dotnet run -- Compile # Run multiple targets dotnet run -- Compile Test -# Pass parameters -dotnet run -- Deploy --configuration Release --api-key sk-123 +# Pass parameters (use a secret provider for real credentials) +dotnet run --project _atom -- Deploy --configuration Release --api-key example-key # Interactive mode (prompt for missing params) dotnet run -- Deploy -i @@ -78,8 +92,11 @@ dotnet run -- Pack -s # Verbose output dotnet run -- Compile -v -# Use a custom project directory -dotnet run -- Compile -p MyBuildProject +# Use a custom project with the global tool +atom --project MyBuildProject Compile + +# Run a file-based build with the global tool +atom --file Build.cs SayHello # Show help dotnet run -- -h @@ -93,7 +110,20 @@ The `atom` global tool (`Invex.Atom.Tool`) provides the same interface but disco atom Compile Test --verbose ``` -It searches for the Atom project in the current directory tree (or the directory specified by `-p`). +It searches the current directory and parent directories, and does not search arbitrary child +directories. Use `--project` or `--file` to select a specific build. + +### Adding a NuGet source + +The `nuget-add` command adds a package source to the user-level NuGet configuration: + +```shell +atom nuget-add my-feed https://example.invalid/nuget/index.json +``` + +If `NUGET_TOKEN_MY_FEED` is set, its value is used as the feed password. This command writes +credentials to the user configuration; review the resulting NuGet configuration and protect it +appropriately. ### Restore & Build Caching @@ -124,4 +154,3 @@ atom Compile --no-restore-cache ATOM_NO_RESTORE_CACHE=1 atom Compile ``` - diff --git a/docs/workflows/overview.md b/docs/workflows/overview.md index 8674465a..9137b218 100644 --- a/docs/workflows/overview.md +++ b/docs/workflows/overview.md @@ -6,31 +6,34 @@ and automatically generate the platform-specific YAML files for GitHub Actions a ## When Do You Need Workflows? Use workflows when you want Atom to **generate** your CI/CD configuration. If you only run builds locally or maintain -your YAML by hand, stick with `BuildDefinition`. +your YAML by hand, stick with `IBuildDefinition`. ## Enabling Workflows -1. Inherit from `WorkflowBuildDefinition` instead of `BuildDefinition`: +1. Extend `IWorkflowBuildDefinition` instead of `IBuildDefinition`: ```csharp [BuildDefinition] [GenerateEntryPoint] - internal partial class Build : WorkflowBuildDefinition + internal interface IBuild : IWorkflowBuildDefinition { // ... } ``` -2. Override the `Workflows` property to declare your pipelines. + The alternative partial-class form derives from `WorkflowBuildDefinition`. + +2. Define the `Workflows` property to declare your pipelines. Interface builds explicitly + implement `IWorkflowBuildDefinition.Workflows`; partial classes override it. 3. Add a platform module (`Invex.Atom.Module.GithubWorkflows` or `Invex.Atom.Module.DevopsWorkflows`) so Atom knows which YAML format to emit. 4. Run `dotnet run -- Gen` to write the files. -## What `WorkflowBuildDefinition` Adds +## What workflow builds add -`WorkflowBuildDefinition` extends `BuildDefinition` with: +Both `IWorkflowBuildDefinition` and `WorkflowBuildDefinition` provide: | Feature | Description | |-------------------------|--------------------------------------------------------------------------------| @@ -51,4 +54,3 @@ your YAML by hand, stick with `BuildDefinition`. ## Next Steps → [Workflow Definitions](workflow-definitions.md) - diff --git a/global.json b/global.json new file mode 100644 index 00000000..9a523dc4 --- /dev/null +++ b/global.json @@ -0,0 +1,7 @@ +{ + "sdk": { + "version": "10.0.0", + "rollForward": "latestMajor", + "allowPrerelease": false + } +} \ No newline at end of file diff --git a/index.md b/index.md index f27da7b0..59b07c1f 100644 --- a/index.md +++ b/index.md @@ -1,8 +1,13 @@ ---- -uid: index ---- +# Atom - +Atom is a type-safe .NET build automation framework. Define build logic in C#, run it locally with +normal .NET tooling, and generate CI/CD workflows for GitHub Actions or Azure DevOps. -Redirecting to [documentation home](README.md)... +## Start here +- [Introduction](docs/getting-started/introduction.md) — understand the framework and package layout. +- [Your First Build](docs/getting-started/your-first-build.md) — create and run a minimal build. +- [Base vs Workflow Build](docs/getting-started/base-vs-workflow-build.md) — choose local builds or workflow generation. +- [CLI Reference](docs/reference/cli.md) — run targets, pass parameters, and configure project discovery. + +See the [full documentation index](docs/toc.yml) or the [README](README.md) for the project overview. diff --git a/src/Invex.Atom.Build/Invex.Atom.Build.csproj b/src/Invex.Atom.Build/Invex.Atom.Build.csproj index 71926a00..d4946b0f 100644 --- a/src/Invex.Atom.Build/Invex.Atom.Build.csproj +++ b/src/Invex.Atom.Build/Invex.Atom.Build.csproj @@ -5,9 +5,9 @@ - - - + + + @@ -17,7 +17,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/Invex.Atom.Module.AzureKeyVault/Invex.Atom.Module.AzureKeyVault.csproj b/src/Invex.Atom.Module.AzureKeyVault/Invex.Atom.Module.AzureKeyVault.csproj index ea4fcf26..792bc99f 100644 --- a/src/Invex.Atom.Module.AzureKeyVault/Invex.Atom.Module.AzureKeyVault.csproj +++ b/src/Invex.Atom.Module.AzureKeyVault/Invex.Atom.Module.AzureKeyVault.csproj @@ -11,7 +11,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/Invex.Atom.Module.AzureStorage/Invex.Atom.Module.AzureStorage.csproj b/src/Invex.Atom.Module.AzureStorage/Invex.Atom.Module.AzureStorage.csproj index 455194f6..9f543d82 100644 --- a/src/Invex.Atom.Module.AzureStorage/Invex.Atom.Module.AzureStorage.csproj +++ b/src/Invex.Atom.Module.AzureStorage/Invex.Atom.Module.AzureStorage.csproj @@ -14,7 +14,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/Invex.Atom.Module.DevopsWorkflows/Invex.Atom.Module.DevopsWorkflows.csproj b/src/Invex.Atom.Module.DevopsWorkflows/Invex.Atom.Module.DevopsWorkflows.csproj index 5bd3693d..57a3995c 100644 --- a/src/Invex.Atom.Module.DevopsWorkflows/Invex.Atom.Module.DevopsWorkflows.csproj +++ b/src/Invex.Atom.Module.DevopsWorkflows/Invex.Atom.Module.DevopsWorkflows.csproj @@ -7,12 +7,12 @@ - + - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/Invex.Atom.Module.Dotnet/Invex.Atom.Module.Dotnet.csproj b/src/Invex.Atom.Module.Dotnet/Invex.Atom.Module.Dotnet.csproj index cefb5f9f..9eda537d 100644 --- a/src/Invex.Atom.Module.Dotnet/Invex.Atom.Module.Dotnet.csproj +++ b/src/Invex.Atom.Module.Dotnet/Invex.Atom.Module.Dotnet.csproj @@ -8,7 +8,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/Invex.Atom.Module.GitVersion/Invex.Atom.Module.GitVersion.csproj b/src/Invex.Atom.Module.GitVersion/Invex.Atom.Module.GitVersion.csproj index e3fd75e2..9750664b 100644 --- a/src/Invex.Atom.Module.GitVersion/Invex.Atom.Module.GitVersion.csproj +++ b/src/Invex.Atom.Module.GitVersion/Invex.Atom.Module.GitVersion.csproj @@ -6,7 +6,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/Invex.Atom.Module.GithubWorkflows/Invex.Atom.Module.GithubWorkflows.csproj b/src/Invex.Atom.Module.GithubWorkflows/Invex.Atom.Module.GithubWorkflows.csproj index c250f971..7105fd11 100644 --- a/src/Invex.Atom.Module.GithubWorkflows/Invex.Atom.Module.GithubWorkflows.csproj +++ b/src/Invex.Atom.Module.GithubWorkflows/Invex.Atom.Module.GithubWorkflows.csproj @@ -7,13 +7,13 @@ - + - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/Invex.Atom.Workflows/Invex.Atom.Workflows.csproj b/src/Invex.Atom.Workflows/Invex.Atom.Workflows.csproj index 04f9f26d..b88429ac 100644 --- a/src/Invex.Atom.Workflows/Invex.Atom.Workflows.csproj +++ b/src/Invex.Atom.Workflows/Invex.Atom.Workflows.csproj @@ -5,7 +5,7 @@ - + @@ -15,7 +15,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/tests/Invex.Atom.Build.SourceGenerators.Tests/Invex.Atom.Build.SourceGenerators.Tests.csproj b/tests/Invex.Atom.Build.SourceGenerators.Tests/Invex.Atom.Build.SourceGenerators.Tests.csproj index 4e8e8160..bf57066b 100644 --- a/tests/Invex.Atom.Build.SourceGenerators.Tests/Invex.Atom.Build.SourceGenerators.Tests.csproj +++ b/tests/Invex.Atom.Build.SourceGenerators.Tests/Invex.Atom.Build.SourceGenerators.Tests.csproj @@ -15,7 +15,7 @@ - + diff --git a/tests/Invex.Atom.Build.Tests/Invex.Atom.Build.Tests.csproj b/tests/Invex.Atom.Build.Tests/Invex.Atom.Build.Tests.csproj index 3d3e9e75..3ae426dd 100644 --- a/tests/Invex.Atom.Build.Tests/Invex.Atom.Build.Tests.csproj +++ b/tests/Invex.Atom.Build.Tests/Invex.Atom.Build.Tests.csproj @@ -20,7 +20,7 @@ - + diff --git a/tests/Invex.Atom.Module.DevopsWorkflows.Tests/Invex.Atom.Module.DevopsWorkflows.Tests.csproj b/tests/Invex.Atom.Module.DevopsWorkflows.Tests/Invex.Atom.Module.DevopsWorkflows.Tests.csproj index f2cf6464..f9bae3d9 100644 --- a/tests/Invex.Atom.Module.DevopsWorkflows.Tests/Invex.Atom.Module.DevopsWorkflows.Tests.csproj +++ b/tests/Invex.Atom.Module.DevopsWorkflows.Tests/Invex.Atom.Module.DevopsWorkflows.Tests.csproj @@ -20,7 +20,7 @@ - + diff --git a/tests/Invex.Atom.Module.GithubWorkflows.Tests/Invex.Atom.Module.GithubWorkflows.Tests.csproj b/tests/Invex.Atom.Module.GithubWorkflows.Tests/Invex.Atom.Module.GithubWorkflows.Tests.csproj index 359b76e0..669da65a 100644 --- a/tests/Invex.Atom.Module.GithubWorkflows.Tests/Invex.Atom.Module.GithubWorkflows.Tests.csproj +++ b/tests/Invex.Atom.Module.GithubWorkflows.Tests/Invex.Atom.Module.GithubWorkflows.Tests.csproj @@ -20,7 +20,7 @@ - + diff --git a/tests/Invex.Atom.Tool.Tests/Invex.Atom.Tool.Tests.csproj b/tests/Invex.Atom.Tool.Tests/Invex.Atom.Tool.Tests.csproj index 05a02253..68772382 100644 --- a/tests/Invex.Atom.Tool.Tests/Invex.Atom.Tool.Tests.csproj +++ b/tests/Invex.Atom.Tool.Tests/Invex.Atom.Tool.Tests.csproj @@ -17,7 +17,7 @@ - + diff --git a/tests/Invex.Atom.Workflows.Tests/Invex.Atom.Workflows.Tests.csproj b/tests/Invex.Atom.Workflows.Tests/Invex.Atom.Workflows.Tests.csproj index 4ec857de..a17751cf 100644 --- a/tests/Invex.Atom.Workflows.Tests/Invex.Atom.Workflows.Tests.csproj +++ b/tests/Invex.Atom.Workflows.Tests/Invex.Atom.Workflows.Tests.csproj @@ -16,7 +16,7 @@ - + diff --git a/toc.yml b/toc.yml index 22f32209..5110bb26 100644 --- a/toc.yml +++ b/toc.yml @@ -2,5 +2,4 @@ href: docs/toc.yml homepage: docs/getting-started/introduction.md - name: API Reference - href: api/ - + href: api/toc.yml