Use this workflow when one repository publishes a PowerShell module and one or more NuGet packages from the same build:
- the PowerShell module through
Build/Build-Module.ps1 - one or more NuGet packages through the project build pipeline
- PowerShell Gallery publishing
- NuGet publishing
- a single GitHub release containing the module, NuGet packages, checksums, and manifest
The important design point is that this is one PowerForge release runtime, not Build-Module.ps1
manually calling Build-Project.ps1.
In this document, "one package" means one coordinated release transaction and one staged GitHub asset set. NuGet and PowerShell Gallery still receive their native package formats because those ecosystems have different contracts.
Repositories such as PSParseHTML commonly have two cleanly separated entrypoints:
Build/Build-Module.ps1declares module manifest, merge, binary module build, signing, and module artifacts through the existingBuild-Module/New-Configuration*DSL.Build/Build-Project.ps1is a thin wrapper overInvoke-ProjectBuild -ConfigPath Build/project.build.json.Build/project.build.jsonowns the HtmlTinkerX NuGet package release settings, NuGet.org publishing, and GitHub project-release settings.
Keep that ownership split. The coordinated module pipeline provides one release-day entrypoint:
Build everything for this version, publish packages to NuGet, publish the module to PSGallery, and upload the final release set to GitHub.
The existing Build-Module {} / New-Configuration* authoring model can declare package builds, release staging,
version coordination, and publishing intent in the same settings block.
PSPublishModule itself uses the canonical JSON-first entry point:
.\Build\Build-Project.ps1The complete repository release uses the same entry point:
.\Build\Build-Project.ps1 -PublishFor compatibility, an unscoped -RunMode Publish request is normalized to the same complete release. It publishes
the coordinated PowerForge package train to NuGet, publishes PSPublishModule to PowerShell Gallery, and creates the
unified GitHub release only after those prerequisite lanes succeed. Use the explicit lane selectors for an
intentional partial operation: -ModuleOnly -RunMode Publish, -PackagesOnly -PublishNuget, or
-ToolsOnly -PublishToolGitHub. Full publication rejects those lane selectors and the lane-specific GitHub
publication switches so a partial public release cannot become visible before the coordinated registries succeed.
Build/Build-Project.ps1 loads Build/release.json, which coordinates the module recipe in powerforge.json
with packages, tools, signing, and the unified GitHub release. External module repositories can continue using
the Build-Module {} DSL; both entry-point styles run the same shared module and release engines.
Repositories that also ship native CLI archives can keep those targets in the broader
Invoke-PowerForgeRelease configuration. Their publish wrapper should select the module Publish gate and the
tool GitHub publisher in the same engine invocation. The module pipeline still owns NuGet, PowerShell Gallery,
and the coordinated module GitHub asset set; the tool lane owns runtime-specific Windows, Linux, and macOS
archives. This is one operator command with one release owner, even when independent product version lines require
separate GitHub tags.
The Build-Module-first workflow supports both JSON-backed and PowerShell-authored package configuration.
This is the lowest-risk bridge for existing repositories. It preserves every option already supported by
project.build.json and keeps CI/schema-driven configuration stable.
Build-Module -ModuleName 'PSParseHTML' {
New-ConfigurationManifest @Manifest
New-ConfigurationBuild @ModuleBuild
New-ConfigurationArtefact @PackedModule
New-ConfigurationProjectBuild `
-Name 'HtmlTinkerX' `
-ConfigPath '.\Build\project.build.json' `
-BuildBeforeModule `
-UseAsReleaseVersionSource `
-ProvideLocalNuGetFeed `
-PublishNuget
New-ConfigurationRelease `
-StageRoot '.\Artefacts\UploadReady' `
-VersionSource ProjectBuild `
-PrimaryProject 'HtmlTinkerX' `
-SynchronizeModuleVersion `
-PublishOrder NuGet, PowerShellGallery, GitHub
New-ConfigurationPublish -Type PowerShellGallery -FilePath 'C:\Support\Important\PowerShellGalleryAPI.txt' -Enabled
New-ConfigurationPublish -Type GitHub `
-FilePath 'C:\Support\Important\GithubAPI.txt' `
-UserName 'EvotecIT' `
-RepositoryName 'PSParseHTML' `
-OverwriteTagName 'PSParseHTML-v<ResolvedReleaseVersion>' `
-GenerateReleaseNotes `
-Enabled
} -ExitCodeNuGet package publishing stays in New-ConfigurationProjectBuild / New-ConfigurationPackageBuild because
that is package-lane behavior. PowerShell Gallery and top-level GitHub publishing stay with module/release
publish configuration.
For direct Build-Module GitHub publishing, an X-pattern version also skips occupied GitHub tags/releases during
publish planning. The publish row switches to determinate byte progress and shows the active asset. Existing releases
are not reused by default. Use -ReuseExistingRelease only for a deliberate recovery run, and add
-ReplaceExistingAssets only when that recovery should replace same-name assets. Protected recovery should also
bind the exact existing release, authorize the complete asset-name set, and verify the server-assigned identity of
every uploaded asset before reporting success.
For modules where you want the whole release in PowerShell, declare the package config inline. It maps to
ProjectBuildConfiguration, exports to JSON, and runs through the same engine.
Build-Module -ModuleName 'PSParseHTML' {
New-ConfigurationManifest @Manifest
New-ConfigurationBuild @ModuleBuild
New-ConfigurationArtefact @PackedModule
New-ConfigurationPackageBuild `
-Name 'HtmlTinkerX' `
-RootPath '.\Sources' `
-ExpectedVersionMap @{ HtmlTinkerX = '2.0.X' } `
-ExpectedVersionMapAsInclude `
-Configuration Release `
-StagingPath '.\Artefacts\ProjectBuild' `
-CleanStaging `
-CreateReleaseZip `
-CertificateThumbprint 'YOUR_CERTIFICATE_THUMBPRINT' `
-CertificateStore CurrentUser `
-PublishNuget `
-PublishSource 'https://api.nuget.org/v3/index.json' `
-PublishApiKeyFilePath 'C:\Support\Important\NugetOrgEvotec.txt' `
-BuildBeforeModule `
-ProvideLocalNuGetFeed `
-UseAsReleaseVersionSource
New-ConfigurationRelease `
-StageRoot '.\Artefacts\UploadReady' `
-VersionSource PackageBuild `
-PrimaryProject 'HtmlTinkerX' `
-SynchronizeModuleVersion `
-PublishOrder NuGet, PowerShellGallery, GitHub
New-ConfigurationPublish `
-Type PowerShellGallery `
-FilePath 'C:\Support\Important\PowerShellGalleryAPI.txt' `
-Enabled
New-ConfigurationPublish `
-Type GitHub `
-UserName 'EvotecIT' `
-RepositoryName 'PSParseHTML' `
-FilePath 'C:\Support\Important\GithubAPI.txt' `
-OverwriteTagName 'PSParseHTML-v<ResolvedReleaseVersion>' `
-GenerateReleaseNotes `
-Enabled
} -ExitCodeThe direct PowerShell route covers the project-build schema with first-class parameters and the -Options
hashtable for additional fields.
New-ConfigurationRelease automatically coordinates the module with -PrimaryProject when ProjectBuild or
PackageBuild is selected as the version source. -SynchronizeModuleVersion remains available as an explicit
declaration for readability. This does not, by itself, assign one version to every NuGet package in the repository;
use AlignPackageVersions or a shared version track for that package group.
For an initial coordinated publish, duplicate NuGet versions are release conflicts rather than successful skips. Duplicate tolerance is enabled only when resuming a checkpoint that proves the same NuGet lane was already attempted, so an occupied package version cannot silently lead to an older PSGallery or GitHub release.
PowerForge makes the version decision in this order:
- Resolve the next available module version from the module X-pattern and module history.
- Use that version as a floor for
-PrimaryProjectin the one package lane marked-UseAsReleaseVersionSource. - Resolve the primary project's normal candidate from its X-pattern and NuGet history.
- Select the higher numeric version. At the same numeric version, a stable X-pattern candidate does not erase a configured module prerelease; explicit prerelease versions retain normal semantic-version ordering.
- If
AlignPackageVersionsis enabled, apply the final version to every project in the primary project's matching ordinary X-pattern group or namedVersionTrackalignment group. - Synchronize the module manifest, artifacts, staging metadata, and publish operations to the final primary-project version.
AlignPackageVersions defaults to false. Omitting it from project.build.json has the same behavior as setting it
to false: the module and PrimaryProject coordinate, while other packages follow their normal project-build version
policy. Ordinary X-pattern entries resolve independently; named VersionTracks already coordinate their members.
| Module synchronization | Package alignment | Result |
|---|---|---|
| Disabled | Disabled | The module uses module history. Ordinary package X-patterns resolve independently; each VersionTrack applies its configured exact version or anchor-derived X-pattern version to all members. |
| Disabled | Enabled | The module still uses module history. Ordinary packages with the same X-pattern align; every named X-pattern VersionTrack remains a separate alignment group, while exact-version tracks remain exact. |
| Enabled | Disabled | The module matches PrimaryProject. Other ordinary package X-patterns resolve independently; VersionTracks continue coordinating their own members. |
| Enabled | Enabled | The module matches PrimaryProject. The primary project's ordinary X-pattern group or named X-pattern VersionTrack receives the coordinated floor; exact-version tracks remain exact and must be compatible with that floor. Other groups remain independent. |
For ordinary ExpectedVersion and ExpectedVersionMap entries, AlignPackageVersions groups projects by their
configured X-pattern. All ordinary projects configured with 2.1.X form one group, while ordinary projects configured
with 1.4.X form another. Each group is stepped from the highest NuGet version found for a package in that group.
The module floor is applied only to the group containing PrimaryProject.
VersionTracks are explicit release trains and define their own boundaries:
- With alignment disabled, each track applies one version to all members: either its configured exact version or an X-pattern version resolved from its configured anchor.
- With alignment enabled, each X-pattern track becomes its own alignment group. Two named X-pattern tracks using the same pattern do not merge with each other or with ordinary projects using that pattern.
- An exact-version track never becomes an alignment group. Its configured exact version is still applied to all members.
- When
PrimaryProjectbelongs to an aligned track, the module floor applies to the whole track. - When alignment is disabled, an anchor-derived track version below the module floor is rejected instead of splitting the track by raising only the primary project.
- When
PrimaryProjectbelongs to an exact-version track, an exact version below the module floor is rejected; a compatible exact version remains exact and is shared by the module and every track member.
Ordinary exact versions outside VersionTracks do not participate in X-pattern alignment. Such an exact version remains
exact unless it belongs to the primary project and is below the coordinated module floor, in which case validation fails
before project files change.
Assume this illustrative package configuration:
{
"ExpectedVersionMap": {
"Mailozaurr": "2.1.X",
"Mailozaurr.Application": "2.1.X",
"Mailozaurr.Cli": "2.1.X"
},
"ExpectedVersionMapAsInclude": true,
"AlignPackageVersions": false
}and these histories:
| Deliverable | Current published version | Normal next candidate |
|---|---|---|
| Mailozaurr module | 2.1.6 |
module floor 2.1.7 |
| Mailozaurr package | 2.0.12 |
2.1.0 |
| Mailozaurr.Application | 2.1.5 |
2.1.6 |
| Mailozaurr.Cli | not published | 2.1.0 |
The resolved release is:
| Deliverable | Resolved version | Reason |
|---|---|---|
| Mailozaurr module | 2.1.7 |
Synchronized to the primary project |
| Mailozaurr package | 2.1.7 |
Raised from 2.1.0 to the module floor |
| Mailozaurr.Application | 2.1.6 |
Keeps its independent NuGet candidate |
| Mailozaurr.Cli | 2.1.0 |
Keeps its independent NuGet candidate |
This is the correct choice when companion packages have independent release histories. The combined GitHub release may
contain packages with different versions; only the module and primary package are guaranteed to match. If the repository
uses VersionTracks, members of each track still share their anchor-derived version even though alignment is disabled.
Using the same histories, change the JSON setting to:
{
"ExpectedVersionMap": {
"Mailozaurr": "2.1.X",
"Mailozaurr.Application": "2.1.X",
"Mailozaurr.Cli": "2.1.X"
},
"ExpectedVersionMapAsInclude": true,
"AlignPackageVersions": true
}The three packages share 2.1.X, so they form one aligned group. Their NuGet-derived group candidate is 2.1.6,
while the module floor is 2.1.7. The module floor wins and the complete release uses:
| Deliverable | Resolved version |
|---|---|
| Mailozaurr module | 2.1.7 |
| Mailozaurr package | 2.1.7 |
| Mailozaurr.Application | 2.1.7 |
| Mailozaurr.Cli | 2.1.7 |
Use alignment when those packages are one release train and should always carry the same version.
If the module floor is 2.1.7 but a package in the aligned 2.1.X group is already published as 2.1.9, the group
candidate becomes 2.1.10. That higher numeric candidate wins:
| Deliverable | Resolved version |
|---|---|
| Module | 2.1.10 |
| Primary package | 2.1.10 |
Other packages in the 2.1.X group |
2.1.10 |
Synchronization therefore protects both histories: module history prevents the package release from moving backward, and NuGet history can move the module release forward.
Consider this configuration:
{
"ExpectedVersionMap": {
"Suite.Core": "2.1.X",
"Suite.Rendering": "2.1.X",
"Suite.LegacyBridge": "1.4.X",
"Suite.Protocol": "3.0.5"
},
"ExpectedVersionMapAsInclude": true,
"AlignPackageVersions": true
}If Suite.Core is PrimaryProject, the module floor applies to the 2.1.X group containing Suite.Core and
Suite.Rendering. Suite.LegacyBridge aligns only within the separate 1.4.X group, and the exact
Suite.Protocol version remains 3.0.5. Enabling alignment does not force these four packages onto one universal
version.
Suppose the configured module prerelease produces a floor of 2.1.7-beta.2, while the package X-pattern produces the
stable numeric candidate 2.1.7. PowerForge keeps 2.1.7-beta.2; the generated stable X-pattern candidate cannot
silently turn a prerelease run into a stable release.
Explicit versions retain normal semantic-version ordering. For example:
2.1.7-beta.1 < 2.1.7-beta.2 < 2.1.7-rc.1 < 2.1.7
A genuinely higher numeric candidate such as 2.1.8 still wins and becomes the final module/package version.
PowerForge rejects coordinated versioning before changing project files when:
PrimaryProjectis missing or does not match a packable project name or package ID.- More than one active lane is marked
UseAsReleaseVersionSource. - The selected lane runs after the module.
- The module and primary package use incompatible version lines, such as module
2.1.Xand package2.0.X. - The primary package uses an exact version below the module floor.
- Version updates are disabled for the selected package lane.
BuildBeforeModule is enough to express dependency order for a single package lane. Use Release.BuildOrder only when
several lanes need an explicit order.
Enable AlignPackageVersions when:
- the packages are released and supported as one versioned product;
- the module consumes those packages as one coordinated dependency set;
- users expect every package asset in the release to carry the same version.
Leave it disabled when:
- companion packages have independent compatibility or release policies;
- a repository intentionally contains several product/version lines;
- only the module and
PrimaryProjectneed to match.
Choose one GitHub publisher. The normal unified shape is:
- package lane:
-PublishNuget - module publish segment:
-Type GitHub - release order:
NuGet,PowerShellGallery,GitHub
The module GitHub operation uploads the staged module and package assets together. Do not also set -PublishGitHub
on New-ConfigurationProjectBuild or New-ConfigurationPackageBuild unless two separate GitHub operations are
intentional.
Both forms use the same model.
JSON remains the canonical interchange format for CI, schema validation, generated configs, and exact round-tripping:
Build-Module -ModuleName 'PSParseHTML' {
New-ConfigurationProjectBuild -ConfigPath '.\Build\project.build.json' -BuildBeforeModule
}PowerShell is the maintainer authoring experience for module repositories:
Build-Module -ModuleName 'PSParseHTML' {
New-ConfigurationPackageBuild -RootPath '.\Sources' -ExpectedVersionMap @{ HtmlTinkerX = '2.0.X' }
}The same model supports:
Invoke-ModuleBuild -JsonOnly -JsonPath .\Artefacts\powerforge.release.json- import/export of the package/release section
- generated
Build/release.jsonfor CI if the repo wants checked-in JSON - pure
Build-Module.ps1for repos that prefer PowerShell DSL only
Use the top-level Validation.AfterStaging action when a repository needs to prove that its module, NuGet packages,
CLI archives, manifest, and checksums work together before any registry or GitHub publication. This action belongs in
powerforge.release.json, not in a module BeforePublish hook, because only the release engine sees the complete asset
set.
{
"Outputs": {
"Staging": {
"RootPath": "../Artefacts/UploadReady/Release"
}
},
"Validation": {
"AfterStaging": [
{
"Name": "Validate release artifacts",
"FilePath": "Test-ReleaseReady.ps1",
"WorkingDirectory": "..",
"TimeoutSeconds": 1800
}
]
}
}PowerForge passes a JSON context file through POWERFORGE_CONTEXT. The context includes the resolved version,
module staging path, release manifest and checksums paths, staging root, and the final staged asset paths. Convenience
environment variables expose the same primary values, including POWERFORGE_RELEASE_VERSION,
POWERFORGE_RELEASE_MANIFEST, and POWERFORGE_MODULE_STAGING_PATH.
The action runs in both Build and Publish modes. Build mode creates and validates the release without publishing it.
Explicit SignAssemblies and SignPackages requests apply to local Build mode without requiring a release
checkpoint. A certificate alone keeps the ordinary Build defaults unsigned. Documentation mode skips package builds,
including their signing and publication steps, even when the package configuration requests them.
Publish mode continues only after every enabled action succeeds, and publishes the exact validated package and module
checkpoints without rebuilding them. A missing script, invalid timeout, non-zero exit code, cancellation, or timeout
fails the release before NuGet, PowerShell Gallery, or GitHub is changed.
Use "release" only for the coordinated top-level artifact set, usually the thing that gets a GitHub tag, manifest, checksums, and user-facing release notes.
For .NET/NuGet work, prefer package-oriented names:
New-ConfigurationProjectBuildfor the JSON-backed bridge to the existing project build engine.New-ConfigurationPackageBuildfor inline package discovery, versioning, pack, sign, and package-publish defaults.New-ConfigurationPackageVersionTrackif the inline DSL needs a focused helper forVersionTracks.New-ConfigurationPublishorNew-ConfigurationReleasePublishfor destinations such as NuGet, PowerShell Gallery, and GitHub.New-ConfigurationReleaseonly for cross-output coordination: staged root, manifest, checksums, tag template, shared version source, build order, and publish order.
That avoids describing .nupkg output as a "release" before it actually participates in the repo-level release.
Source-state and provenance checks are opt-in. Signing a module or publishing it to GitHub does not enable them.
When no ReleaseProtection segment is configured, the module pipeline does not require a clean checkout, does not
compare release inputs with the planned source snapshot, and does not add PowerForge provenance files to packed
modules. Reserved provenance files left by an earlier protected run are removed from package staging while the
feature is disabled.
Choose only the protection the release needs:
# Check only when the plan is created.
New-ConfigurationReleaseProtection -RequireCleanSource
# Keep the release inputs clean and unchanged through packaging and publication.
New-ConfigurationReleaseProtection -RequireSourceUnchanged
# Add signed provenance to eligible signed GitHub module artefacts.
New-ConfigurationReleaseProtection -GenerateProvenanceRequireSourceUnchanged implies RequireCleanSource. GenerateProvenance implies both source checks and also
requires module signing, at least one artefact, and a GitHub release destination. Manifest, Documentation, and
Build gate runs leave provenance generation and its implied checks inactive; an explicitly selected clean or
unchanged-source check still applies in those modes. The equivalent JSON segment is:
{
"Type": "ReleaseProtection",
"Configuration": {
"RequireCleanSource": false,
"RequireSourceUnchanged": false,
"GenerateProvenance": true
}
}- Do not make
Build-Module.ps1responsible for NuGet package publishing mechanics. - Do not make
Build-Project.ps1know module artifact layout or PSGallery publishing. - Do not duplicate signing, GitHub release, tag conflict, checksums, or staging logic in consumer repos.
- Do not require publishing intermediate NuGet packages before the module can build.
Keep three layers:
-
Module lane
- Existing
Invoke-ModuleBuild/Build-ModuleDSL stays the source for module build details. - Module publish settings can stay as
New-ConfigurationPublish -Type PowerShellGalleryfor direct module builds. - Unified release may override whether the module is built, signed, published, or only staged.
- Existing
-
Package lane
- Existing
Invoke-ProjectBuild/project.build.jsonremains the source for NuGet package discovery, versioning, packing, signing, and NuGet.org publishing. - In unified mode, disable package GitHub publishing when top-level GitHub publishing is active, so assets are uploaded once.
- Existing
-
Release lane
New-ConfigurationReleaseowns the module-pipeline coordination slice: staged root and the single top-level GitHub asset set.- The broader
Invoke-PowerForgeReleaseengine remains the long-term owner for checksums, release manifests, and richer graph planning. BuildBeforeModuleand optionalRelease.BuildOrderdeclare phase order instead of relying on script call order.
The release runtime executes these phases:
- Load and validate every section.
- Resolve one release version from
VersionTracks,ExpectedVersionMap, explicit module version, or a declared primary package. Publish-mode auto-versioning also skips occupied unified GitHub tags/releases. - Plan packages and module before doing any destructive or publishing work.
- If the module needs packages from this same release, build packages to a local staging feed first.
- Build the module against either project references or the staged local package feed.
- Stage all module, package, tool, installer, and other selected assets into one upload-ready folder.
- Write
release-manifest.jsonandSHA256SUMS.txtfor that staged set. - Run every enabled
Validation.AfterStagingaction against the complete staged release. - Publish the validated NuGet package files.
- Publish the checkpointed module to PSGallery or the configured private gallery without rebuilding it.
- Publish one GitHub release from the staged asset list, with visible per-asset byte progress and an explicit created-versus-recovery result.
This keeps publication fail-fast while avoiding a half-published release where NuGet is updated before the module can even be built.
Build-Module {} keeps its current module-only behavior when no project/release segments are present.
When New-ConfigurationProjectBuild, New-ConfigurationPackageBuild, or New-ConfigurationRelease is
present, Invoke-ModuleBuild now keeps those segments in the module pipeline plan:
- collect normal module segments into the existing module pipeline spec
- collect project/release segments into package/release plan sections
- export those segments with the module JSON plan when
-JsonOnlyis used - execute
BuildBeforeModulepackage lanes before module staging - inject package output folders as additional NuGet restore sources when
ProvideLocalNuGetFeedis enabled - stage module and package file assets when
Release.StageRootis configured - publish one top-level GitHub release from the staged asset list when a GitHub
Publishsegment is enabled
That means consumer repos can stay with one script and one DSL block, while PowerForge still keeps one reusable implementation for package build, module build, and GitHub publishing.
New-ConfigurationProjectBuildreferences existingproject.build.jsonfiles fromBuild-Module {}.New-ConfigurationPackageBuilddeclares inline package build settings with first-class parameters for the current project-build options and-Optionsfor additional fields.New-ConfigurationReleasedeclares repo-level coordination settings such as stage root, version source, primary project, build order, and publish order.ModulePipelineSpecJSON serialization and deserialization preserve these segments.ModuleBuildPreparationServiceresolves and exports package/release paths relative to the module project root while leaving secret-file paths untouched.ModulePipelinePlankeeps enabled package/release segments explicitly instead of silently dropping them.ModulePipelineRunnerruns enabledProjectBuildandPackageBuildlanes withBuildBeforeModulebefore module staging/build starts, and exposes the resultingProjectBuildHostExecutionResultvalues inModulePipelineResult.ProjectBuildResults.- Inline
PackageBuildsettings map back into the existing sharedProjectBuildConfigurationand execute throughProjectBuildHostService; cmdlets remain thin DSL emitters. ProvideLocalNuGetFeednow requires built.nupkgoutputs and appends their directories toModuleBuildSpec.NuGetRestoreSources.- Module binary publish uses
RestoreAdditionalProjectSourcesso release-local packages are added without discarding normal project or NuGet.config sources. - A
Releasesegment plus an enabled GitHubPublishsegment stages module artifacts and.nupkgoutputs underStageRoot(modulesandnuget) and publishes one GitHub release from that combined asset list. - Release staging writes
metadata/release-manifest.jsonandmetadata/SHA256SUMS.txtand includes them in the top-level GitHub asset list. ModulePipelineResult.ReleaseCoordinationResultreports the staged module/package assets and GitHub result.Release.VersionSource,PrimaryProject, lane-levelUseAsReleaseVersionSource, andSynchronizeModuleVersionproduce one module/package version decision.- Publish runs preflight the synchronized module version before the first remote package publish.
- A credential-free checkpoint records exact project versions and completed destinations for explicit recovery.
A normal new invocation archives any incomplete checkpoint and computes a fresh release; configure
ResumeIncompleteReleaseonly when the operator intentionally wants to continue that exact release and skip destinations that already completed.
Use run modes through the same JSON-first wrapper in CI and at release time:
Manifest: refresh module metadata without package builds or publishing.Build: resolve coordinated versions, build packages, feed them to the module build, and stage artifacts without publishing.Publish: rebuild or reuse the exact coordinated package state and publish inRelease.PublishOrder.
For PSPublishModule's self-build, the practical sequence is:
.\Build\Build-Project.ps1 -RunMode Manifest -ModuleOnly
.\Build\Build-Project.ps1 -RunMode Build
.\Build\Build-Project.ps1 -PublishBuild-Project.ps1 is also the canonical interactive publish entrypoint for this repository.
It publishes the versions and content in the current working tree; it does not require a clean
checkout, a committed version bump, or an exact Git commit. Changing the script's default
RunMode from Build to Publish is equivalent to passing -Publish and is supported for the
maintainer's F5 workflow.
The release still signs configured outputs, validates the module, signs NuGet packages, writes archive and executable SHA-256 values to the standalone tool manifest, and publishes the unified GitHub release last. For this entrypoint, Git commit metadata is optional and is not a publication gate.
Release plans are written under the ignored repository-level Artefacts directory so
machine-specific absolute paths do not create tracked release churn.
The Build gate should report the same resolved version for the primary package and module, list the local NuGet
source added for the module build, and show the combined module/package assets under Release.StageRoot.
Make Build-Module {} the primary authoring surface for module repositories, and let it declare project
packages, release staging, and publish targets through proper New-Configuration* segments.
The important boundary is internal: those segments should translate into the shared unified release model instead of making module build reimplement NuGet, PSGallery, and GitHub release mechanics.
That gives PSParseHTML the one-button release you want while keeping:
- module build rules in the module DSL
- NuGet package rules in project build
- shared release/version/publish behavior in PowerForge
- GitHub release upload as one final staged asset transaction