This is the release policy for ScriptMessage: how versions are numbered, how branches are used, how the
changelog is kept, and how a release is published. It applies to every maintainer and contributor.
CONTRIBUTING.md covers everyday contributions, and the CHANGELOG.md section of
AGENTS.md holds the detailed rules for writing changelog entries.
ScriptMessage follows Semantic Versioning 2.0.0 for every release after 1.1.0. Earlier releases did not follow it strictly (some patch releases contained breaking changes), and their numbers stay as they are.
| Change to the public contract | Increment | Example |
|---|---|---|
| Breaking: an existing script or configuration file can stop working | Major | 1.4.2 to 2.0.0 |
| New backward-compatible functionality, including a new optional parameter | Minor | 1.4.2 to 1.5.0 |
| Backward-compatible fix | Patch | 1.4.2 to 1.4.3 |
The PowerShell Gallery's own publishing guidelines give new parameters as a patch-level example. ScriptMessage uses the stricter Semantic Versioning rule: new functionality, including a new parameter, is a minor release.
The public contract is anything a caller can observe or depend on:
- Exported functions and aliases, their parameters and parameter sets, and each parameter's requirements, defaults, validation, and meaning.
- Pipeline input and binding.
- Output objects, including the properties of the
Send-ScriptMessageresult. - Errors, and whether they terminate.
- Supported PowerShell editions and versions (Windows PowerShell 5.1 and PowerShell 7) and supported operating systems.
- Configuration file settings, their meaning, and their defaults.
- The external modules the module requires, such as the Microsoft Graph sub-modules each message type needs.
- The permissions the module requests or needs, such as Microsoft Graph scopes.
- A fix that changes observable behavior is a patch when the old behavior could not have worked for anyone, such as rejecting recipients that have no address, which never sent anything. It is a breaking change when a reasonable script could have depended on the old behavior. Either way, the changelog entry says what changed.
- Requiring a new module or permission is a breaking change, because existing installations and app registrations stop working until someone acts.
- Deprecate before removing. Mark functionality as deprecated in a minor release (documentation, a warning,
and a
Deprecatedchangelog entry), and remove it no earlier than the next major release. - If a breaking change ships in a minor or patch release by mistake, do not replace or unlist that version. Release a new version that restores compatibility as soon as possible.
- Do not choose the version early. Changes collect under
[Unreleased]inCHANGELOG.md, and the version is chosen at release time from what is there: anything breaking means major, anything added means minor, and only fixes means patch.
ModuleVersioninScriptMessage/ScriptMessage.psd1is the source of truth, and has exactly three numeric parts. It keeps the last released version until a release-prep pull request changes it.- A prerelease also sets
PrereleaseunderPrivateData.PSData. For a stable release, leave that key commented out rather than setting it to an empty string. - The Git tag, the changelog heading, the GitHub release, and the PowerShell Gallery version all use the same
string, with no
vprefix:1.2.0, or2.0.0-rc01for a prerelease. The release workflow refuses to publish when they disagree.
- A prerelease label may contain only letters, digits, and hyphens. The PowerShell Gallery rejects dots and
+. - Use
alpha, thenbeta, thenrc, each with a two-digit number:2.0.0-alpha01,2.0.0-beta01,2.0.0-rc01. The Gallery compares labels as text, so without the leading zerorc10would sort beforerc2. - Installing a prerelease needs PowerShellGet 1.6.0 or later with
-AllowPrerelease, or PSResourceGet with-Prerelease. The PowerShellGet built into Windows PowerShell 5.1 cannot install one. A prerelease and the final version install into the same folder, so the final version replaces the prerelease.
A released version is never republished, re-tagged, or replaced, and its number is never reused. The PowerShell Gallery enforces this on its side: packages cannot be deleted, only unlisted, and each publish must be a higher version than the last. If a release is defective, fix it in a new version.
ScriptMessage uses the Simple Modified Gitflow workflow: Gitflow without release branches, because only one version is maintained at a time.
| Branch | Created from | Merges into | Purpose |
|---|---|---|---|
main |
Permanent | None | Always the latest release. Every merge into main is a release and is tagged. |
develop |
Permanent | main |
The integration branch, holding the complete history of the project. |
feature/<short-name> |
develop |
develop |
New features, and bug fixes that can wait for the next release. |
hotfix/<version> |
main |
main, then develop |
Urgent fixes to the released version. |
Rules for the shared branches:
- Changes reach
developandmainonly through pull requests, and the tests must pass first. - Merge into
mainwith a merge commit, never a squash or a rebase. A squashed commit onmaindoes not exist ondevelop, so every later release pull request would conflict with it. - Never rebase or force-push
mainordevelop. Release tags cannot move, and rewriting shared history breaks every contributor's clone. Rebase only your own feature branch, and only before it merges. - After every release or hotfix, merge
mainback intodevelop, sodevelopcontains the release commit. - Stable release tags go only on
main. Prerelease tags may go ondevelop.
CHANGELOG.md follows Keep a Changelog 1.1.0. Every pull request with a
user-visible change adds its entry under ## [Unreleased], in one of these sections, in this order: Added,
Changed, Deprecated, Removed, Fixed, Security. The rules for what an entry says are in the
CHANGELOG.md section of AGENTS.md, and they apply to everyone, not only to AI assistants.
[Unreleased] exists only on develop, and only while it has entries. A release renames it to the version
heading, so right after a release there is none. The next pull request that adds an entry also adds the section at
the top, linked to a comparison from the last release:
## [Unreleased](https://github.com/Sekers/ScriptMessage/compare/1.2.0...develop). The Release workflow refuses to
publish a tag whose CHANGELOG.md still has an [Unreleased] section.
-
Choose the version from the
[Unreleased]entries, following Deciding hard cases. -
Open a release-prep pull request into
developthat changes only:ModuleVersion, andPrereleasefor a prerelease, inScriptMessage/ScriptMessage.psd1.CHANGELOG.md: rename## [Unreleased](...)to a dated heading such as## [1.2.0](https://github.com/Sekers/ScriptMessage/tree/1.2.0) - 2026-09-20. Do not add a new[Unreleased]section.
-
Open a pull request from
developintomain, and merge it with a merge commit once the checks pass. Merge nothing else intodevelopuntil then, so no new[Unreleased]section reachesmain. -
Tag the merge commit with an annotated tag (add
-sto sign it when you have signing set up), and push the tag:git fetch origin git tag -a 1.2.0 origin/main -m 'ScriptMessage 1.2.0' git push origin 1.2.0
-
The Release workflow (
.github/workflows/Release.yml) runs these jobs in order and stops at the first failure:- Confirms the tag is annotated, matches the manifest version, has a dated
CHANGELOG.mdsection and no[Unreleased]section, and points at a commit onmain(or ondevelop, for a prerelease). - Runs the test suite (
.github/workflows/Tests.yml). - Builds the package once and publishes it to the PowerShell Gallery, in the
psgalleryenvironment. - Creates the GitHub release from the version's changelog section, attaches the same package, and publishes the release.
- Confirms the tag is annotated, matches the manifest version, has a dated
-
Merge
mainback intodevelop.
The GitHub release is created last so that a failed check or publish never leaves behind a public release, which immutable releases would lock, for a version that did not ship.
- Job 1, 2, or 3 failed: the Gallery has not accepted the package and no GitHub release exists. Fix the problem through pull requests, delete the tag (a repository admin must bypass the tag ruleset), and tag the new merge commit with the same version.
- Only job 4 failed: the version has shipped to the Gallery. Use Re-run failed jobs so only the GitHub release is retried. Do not re-run all jobs, because the Gallery rejects a second publish of the same version.
- Create
hotfix/<version>frommain, for examplehotfix/1.2.1. - Fix the problem, add tests, bump the patch version, and add a dated changelog section for the hotfix.
- Open a pull request into
main, merge it with a merge commit, and tag and push as in Releasing a version, steps 4 and 5. - Merge
mainintodevelop. InCHANGELOG.md, keep the[Unreleased]section fromdevelopat the top, with the hotfix's section below it.
A prerelease follows the same steps, with three differences: the release-prep pull request also sets
Prerelease, the changelog heading names the prerelease (## [2.0.0-rc01](...) - 2026-09-20), and the tag goes
on the release-prep merge commit on develop instead of on main. When the final version ships, its changelog
section covers everything since the last stable release, not only the changes since the last prerelease.
A repository admin configures these once in the GitHub repository settings. They are what make the rules above enforceable:
- Branch rulesets for
mainanddevelop: require a pull request, require theWorkflow lintandPesterchecks to pass, and block force pushes and deletion. Formain, allow only merge commits. - A tag ruleset for release tags: target tags that look like versions, and block updates and deletion, with bypass only for admins.
- Immutable releases: enabled, so a published release's tag and assets cannot change.
- A
psgalleryenvironment: holds thePS_GALLERY_KEYsecret, is limited to release tags, and can require a maintainer's approval. Remove any repository-level copy of the secret. Use a PowerShell Gallery API key scoped to the ScriptMessage package with only the "Push new or update packages" permission. Gallery API keys expire after at most 365 days, the Gallery emails the key's owner 10 days before expiry, and an expired key fails the publish job.
- Every change listed under
[Unreleased]has been reviewed and merged intodevelop. - The version increment follows Versioning.
- The release-prep pull request changed only the manifest version and the changelog heading, and added no
[Unreleased]section. -
developwas merged intomainwith a merge commit. - The tag is annotated, has no
vprefix, and is on the merge commit. - Every job in the Release workflow passed.
- The version appears on the PowerShell Gallery, and the GitHub release has the right notes and package.
-
mainwas merged back intodevelop. - Anything that needs a live Microsoft Graph test was tested separately in a test tenant. The automated tests never connect to a tenant.