diff --git a/src/docs/Modules/Process-PSModule/index.md b/src/docs/Modules/Process-PSModule/index.md index e963bbe..42fc807 100644 --- a/src/docs/Modules/Process-PSModule/index.md +++ b/src/docs/Modules/Process-PSModule/index.md @@ -11,5 +11,6 @@ This section documents how module repositories are formed and how they move from - [Build, Test, Pack, Publish](build-test-pack-publish.md) - [Template Quickstart](template-quickstart.md) - [Module Bootstrap](module-bootstrap.md) +- [Release Branch Pattern](release-branch-pattern.md) For broader framework context, see [MSX Frameworks / Process-PSModule](https://msxorg.github.io/docs/Frameworks/Process-PSModule/). diff --git a/src/docs/Modules/Process-PSModule/module-bootstrap.md b/src/docs/Modules/Process-PSModule/module-bootstrap.md index 143f793..f574c68 100644 --- a/src/docs/Modules/Process-PSModule/module-bootstrap.md +++ b/src/docs/Modules/Process-PSModule/module-bootstrap.md @@ -55,6 +55,6 @@ A module bootstrapped this way: ## When to use this - The module has no usable release yet, and the load-bearing core hasn't landed. -- Use this only for the initial bootstrap. Once `main` has a first release, ongoing feature work targets `main` directly with ordinary topic branches, or a [stacked pull request](https://msxorg.github.io/docs/Ways-of-Working/Branching-and-Merging/#stacked-pull-requests) when changes genuinely depend on each other. +- Use this only for the initial bootstrap. Once `main` has a first release, ongoing feature work targets `main` directly with ordinary topic branches, batches independent features through the [Release Branch Pattern](release-branch-pattern.md), or uses a [stacked pull request](https://msxorg.github.io/docs/Ways-of-Working/Branching-and-Merging/#stacked-pull-requests) when changes genuinely depend on each other. For the general branching and merge model, see [MSX Branching and Merging](https://msxorg.github.io/docs/Ways-of-Working/Branching-and-Merging/). diff --git a/src/docs/Modules/Process-PSModule/release-branch-pattern.md b/src/docs/Modules/Process-PSModule/release-branch-pattern.md new file mode 100644 index 0000000..703ad83 --- /dev/null +++ b/src/docs/Modules/Process-PSModule/release-branch-pattern.md @@ -0,0 +1,56 @@ +# Release Branch Pattern + +Use a release branch to concentrate several related feature changes into one release, when the changes are independent of each other but should ship, close their issues, and generate release notes together. + +This differs from [Module Bootstrap](module-bootstrap.md), which exists only to get a brand-new module's load-bearing core to its first release, and from a [stacked pull request](https://msxorg.github.io/docs/Ways-of-Working/Branching-and-Merging/#stacked-pull-requests), where each layer depends on the one before it. The release branch pattern applies to any module that already has a release, for a batch of independent features destined for the same version bump. + +## Pattern + +1. Cut one release branch from `main`, named for the release, e.g. `release-v1`. +2. Open the **release pull request** immediately: release branch → `main`, as a **draft**. This PR stays open for the lifetime of the release and accumulates the `Closes #N` references for every issue the release resolves. +3. Cut each feature branch from the release branch, not from `main`. +4. Open each **feature pull request** targeting the release branch, not `main`. Feature PRs can land in parallel and in any order relative to each other, unless one feature explicitly depends on another (in which case stack those two normally). +5. Merge feature PRs into the release branch as they become ready. +6. Once every planned feature PR has merged into the release branch, un-draft the release PR and merge it into `main`. This merge becomes the release. + +```mermaid +gitGraph + commit id: "main" + branch release-v1 + checkout release-v1 + branch feature-a + branch feature-b + checkout feature-a + commit id: "PR: feature A" + checkout feature-b + commit id: "PR: feature B" + checkout release-v1 + merge feature-a + merge feature-b + checkout main + merge release-v1 id: "release PR" +``` + +## Release pull request body + +The release PR is the single place that: + +- Lists `Closes #N` for every issue resolved by any feature PR merged into the release branch. +- Carries the combined release notes, written per [MSXOrg PR Format](https://msxorg.github.io/docs/Ways-of-Working/PR-Format.md), once every feature has landed. + +Feature PRs still follow PR Format for their own description, but do not need to duplicate `Closes #N` — the release PR is the one that closes issues when it merges to `main`. + +## Merge order + +- Merge feature PRs in dependency order. Independent feature PRs have no required order between them. +- The release PR always merges last, after every feature PR it depends on has landed in the release branch. +- Keep the release PR in draft for as long as feature PRs are still expected. Un-draft it only once the release branch is feature-complete and passing checks. + +## When to use this + +- The module already has a stable release, and several independent features are queued for the next version. +- Skip this pattern for a single isolated change — use an ordinary topic branch targeting `main` directly. +- Skip this pattern for a brand-new module with no release yet — use [Module Bootstrap](module-bootstrap.md) instead. +- Use a [stacked pull request](https://msxorg.github.io/docs/Ways-of-Working/Branching-and-Merging/#stacked-pull-requests) instead when changes genuinely depend on each other in sequence, rather than being independent features batched into one release. + +For the general branching and merge model, see [MSX Branching and Merging](https://msxorg.github.io/docs/Ways-of-Working/Branching-and-Merging/). diff --git a/src/docs/Modules/Standards.md b/src/docs/Modules/Standards.md index 0124b4e..71107d3 100644 --- a/src/docs/Modules/Standards.md +++ b/src/docs/Modules/Standards.md @@ -311,9 +311,21 @@ Each `*.Tests.ps1` file must declare the Pester 6 requirement: #Requires -Modules @{ ModuleName = 'Pester'; ModuleVersion = '6.0.0'; MaximumVersion = '6.*' } ``` -Tests run against the built module artifact installed locally, across a multi-OS matrix (Linux, macOS, Windows). The full suite must also remain runnable locally without mandatory cloud resources, special access, or secrets that cannot be mocked. +Tests run against the **built and imported module artifact**, never against source files under `src/`. The Process-PSModule pipeline builds the module first, imports the built module, and only then runs the test matrix, across a multi-OS matrix (Linux, macOS, Windows). Test files must not build or import the module themselves — they assume the module is already imported into the session by the framework (or by the contributor locally, see below) and call its exported commands directly. -Unit tests mock boundaries owned by the module, not third-party APIs or SDKs directly. Wrap external dependencies behind a thin module-owned interface and fake that interface. Where the external contract matters, back the unit tests with a small integration suite that exercises the live dependency. +The full suite must also remain deterministic and runnable locally without mandatory cloud resources, special access, or secrets that cannot be faked. See [Running tests locally](#running-tests-locally). + +**No mocks.** Tests use real inputs and real expected outputs against the actual exported commands — see [Test Specification](Test-Specification.md#5-test-guidelines). Where a command depends on an external system that cannot be exercised in CI (a live third-party API, for example), that dependency is wrapped behind a thin module-owned interface, and a small integration suite exercises the live dependency directly, rather than substituting a mock into the unit tests. + +A single consolidated `tests/.Tests.ps1` file (the **Simple** profile below) is the preferred layout for a module. Only split into the **Standard** or **Advanced** profile when the consolidated file becomes too large or unrelated to read as one suite. + +### Running tests locally + +1. Build the module: `./build.ps1` (or the repository's equivalent local build entry point under `tools/*.ps1`). +2. Import the built artifact, not the source: `Import-Module ./output//.psd1 -Force`. +3. Run the suite: `Invoke-Pester -Path ./tests/ -CI`. + +Never run `Invoke-Pester` against `src/` or with the module imported via a source-loading helper — that does not reproduce what CI validates. ### Shared test infrastructure @@ -322,6 +334,8 @@ Tests run in parallel across multiple OS runners. When integration tests need sh - `tests/BeforeAll.ps1` — special root workflow phase that runs once before the full test matrix. Create shared resources here. - `tests/AfterAll.ps1` — special root workflow phase that runs once after the full test matrix. Remove shared resources here. +`tests/BeforeAll.ps1` is a shared setup entry point owned by the framework, not a place to import or build the module — the framework already guarantees the built module is imported before any test phase runs. Use it only for shared external resources (test fixtures, live service state) that every test file in the matrix depends on. + These exact root files are detected separately and are not recursive test entries. Nested files with these names do not create workflow phases. Use `$env:GITHUB_RUN_ID` (stable per workflow run, shared across all runners) for deterministic resource names: diff --git a/src/docs/Modules/Test-Specification.md b/src/docs/Modules/Test-Specification.md index d08eb9b..7b824a6 100644 --- a/src/docs/Modules/Test-Specification.md +++ b/src/docs/Modules/Test-Specification.md @@ -4,6 +4,8 @@ This document defines the structure and guidelines for writing Pester tests for PowerShell functions. The goal is to ensure consistency and comprehensive test coverage while maintaining clarity. +Tests run against the **built and imported module**, not against source files, and test files do not bootstrap or import the module themselves. See [Standards § Tests](Standards.md#tests) for the full process: file layout, running tests locally, and the no-mocks rule. + ## Test Structure Each function is tested within a structured Pester `Describe` block that follows this hierarchy: diff --git a/src/zensical.toml b/src/zensical.toml index ad6c524..58f9fee 100644 --- a/src/zensical.toml +++ b/src/zensical.toml @@ -34,6 +34,7 @@ nav = [ {"Build, Test, Pack, Publish" = "Modules/Process-PSModule/build-test-pack-publish.md"}, {"Template Quickstart" = "Modules/Process-PSModule/template-quickstart.md"}, {"Module Bootstrap" = "Modules/Process-PSModule/module-bootstrap.md"}, + {"Release Branch Pattern" = "Modules/Process-PSModule/release-branch-pattern.md"}, ]}, ]}, {"Dictionary" = [