Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- <#
- .SYNOPSIS
- Bumps a project's own version across its release-metadata files (in sync,
- leaving all dependency constraints untouched), then commits the changed
- files, creates a lightweight git tag named exactly for that version, and
- pushes the branch and tag.
- .DESCRIPTION
- Updates only the project's own version field in each file that is present:
- library.properties version=<x>
- idf_component.yml top-level version: "<x>" (nested dep versions ignored)
- library.json top-level "version": "<x>"
- CMakeLists.txt project(<name> ... VERSION <x>) (any project name)
- Dependency versions (depends=..., GIT_TAG, FIND_PACKAGE_ARGS, nested
- version: constraints, "pkg": "^1.2.3", etc.) are deliberately left alone.
- Missing files are skipped; a file whose version field can't be found is
- reported as a warning and left unchanged rather than guessed at.
- After a successful update it (unless -SkipGit):
- 1. stages EVERYTHING in the working tree (git add -A: new, modified,
- and deleted files across the repo - not just the version files),
- 2. commits it all with the release message,
- 3. creates a lightweight tag whose name is the version verbatim
- (optionally prefixed with -TagPrefix),
- 4. pushes the current branch and then the tag to -Remote.
- NOTE: because step 1 uses `git add -A`, anything currently in your working
- tree goes into the release commit. Use .gitignore to keep out anything you
- don't want committed (build output, local files, etc.).
- Git prerequisites (git present, folder is a work tree, remote exists, tag
- not already present) are checked BEFORE any file is edited, so a bad setup
- won't leave a half-modified tree.
- .PARAMETER Version
- The new version string, e.g. 2.0 or 1.4.3. Must start with a digit.
- Positional #1.
- .PARAMETER CommitMessage
- The commit message, positional #2. A multi-word message must be quoted:
- submit_proj 1.2.0 "this is a commit message"
- If omitted, defaults to "Release <version>".
- .PARAMETER Path
- The project folder to operate on. Named only. Defaults to the current
- directory.
- .PARAMETER DryRun
- Show what would change and which git commands would run, without writing
- files or touching the repository.
- .PARAMETER SkipGit
- Update the files only; do not commit, tag, or push.
- .PARAMETER Remote
- Git remote to push to. Default: origin.
- .PARAMETER TagPrefix
- Optional prefix for the tag name (e.g. 'v' -> tag 'v2.0'). Default: none,
- so the tag is the version exactly as given.
- .EXAMPLE
- .\submit_proj.ps1 1.2.0 "this is a commit message"
- Bump present files to 1.2.0, commit everything with that message, tag '1.2.0', push.
- .EXAMPLE
- .\submit_proj.ps1 2.0
- Same, using the default commit message "Release 2.0".
- .EXAMPLE
- .\submit_proj.ps1 1.5.0 "preview only" -DryRun
- Preview the file edits and git commands without doing anything.
- .EXAMPLE
- .\submit_proj.ps1 1.6.0 -SkipGit
- Only rewrite the version files; leave committing and tagging to you.
- #>
- #Requires -Version 5.1
- [CmdletBinding()]
- param(
- [Parameter(Mandatory, Position = 0)]
- [string]$Version,
- # Positional #2: the commit message. A normal string, so a multi-word
- # message must be quoted: submit_proj 1.2.0 "this is a commit message"
- # Omit it to default to "Release <version>".
- [Parameter(Position = 1)]
- [string]$CommitMessage,
- # Optional flags below - used only when named, so they never interfere
- # with the plain "submit_proj <version> <message>" call.
- [string]$Path = (Get-Location).Path,
- [switch]$DryRun,
- [switch]$SkipGit,
- [string]$Remote = 'origin',
- [string]$TagPrefix = ''
- )
- $ErrorActionPreference = 'Stop'
- # Default the message when it wasn't supplied.
- if (-not $CommitMessage) { $CommitMessage = "Release $Version" }
- # --- validate the version string -------------------------------------------
- if ($Version -notmatch '^[0-9][0-9A-Za-z.\-+]*$') {
- Write-Error "Version '$Version' is invalid. It must start with a digit and use only 0-9 A-Z a-z . - +"
- return
- }
- if ($Version -notmatch '^[0-9]+(\.[0-9]+){1,3}$') {
- Write-Warning "Version '$Version' isn't plain numeric x.y[.z] - CMake and Arduino may reject it."
- }
- if (-not (Test-Path -LiteralPath $Path -PathType Container)) {
- Write-Error "Project folder not found: $Path"
- return
- }
- $root = (Resolve-Path -LiteralPath $Path).Path
- $tag = "$TagPrefix$Version"
- $doGit = -not $SkipGit
- # --- git preflight (BEFORE editing any files) ------------------------------
- # In a real run, a failed check aborts before the tree is touched.
- # In -DryRun, checks only warn so the preview can still run.
- function Test-GitStep {
- param([bool]$Ok, [string]$Message)
- if ($Ok) { return $true }
- if ($DryRun) { Write-Warning "$Message (git steps will be skipped in this preview)"; return $false }
- Write-Error $Message
- exit 1
- }
- if ($doGit) {
- if (-not (Get-Command git -ErrorAction SilentlyContinue)) {
- $doGit = Test-GitStep $false "git was not found on PATH."
- }
- }
- if ($doGit) {
- $inside = (& git -C $root rev-parse --is-inside-work-tree 2>$null)
- $doGit = Test-GitStep (($LASTEXITCODE -eq 0) -and ($inside -eq 'true')) "'$root' is not inside a git working tree."
- }
- if ($doGit) {
- & git -C $root remote get-url $Remote 1>$null 2>$null
- $doGit = Test-GitStep ($LASTEXITCODE -eq 0) "Git remote '$Remote' is not configured in this repo."
- }
- if ($doGit) {
- $existingTag = (& git -C $root tag --list $tag 2>$null)
- # A duplicate tag is fatal in a real run; in a preview we only warn.
- if ($existingTag) { $doGit = Test-GitStep $false "Tag '$tag' already exists in this repo." }
- }
- # --- per-file replacement rules --------------------------------------------
- # Each regex has a named group 'v' = current project version (for reporting).
- # Anchoring guarantees only the project's OWN version can match - never a
- # dependency constraint. Nothing here keys off the project name.
- $rules = @(
- @{
- File = 'library.properties'
- # No $ anchor: it would choke on a Windows CRLF (\r) left before \n.
- # \uFEFF? tolerates a byte-order mark; value runs to end of line.
- Regex = [regex]'(?m)^(?<pre>\uFEFF?version[ \t]*=[ \t]*)(?<v>[^\r\n]*)'
- Eval = { param($m) $m.Groups['pre'].Value + $Version }
- },
- @{
- File = 'idf_component.yml'
- # Column-0 'version:' only (nested dep versions are indented, so ignored).
- # No $ anchor, for the same CRLF reason; the newline is left untouched.
- Regex = [regex]'(?m)^(?<pre>\uFEFF?version:[ \t]*)(?<q>[''"]?)(?<v>[^''"\r\n]*)\k<q>'
- Eval = { param($m) $m.Groups['pre'].Value + $m.Groups['q'].Value + $Version + $m.Groups['q'].Value }
- },
- @{
- File = 'library.json'
- Regex = [regex]'(?<pre>"version"[ \t]*:[ \t]*")(?<v>[^"]*)(?<post>")'
- Eval = { param($m) $m.Groups['pre'].Value + $Version + $m.Groups['post'].Value }
- },
- @{
- File = 'CMakeLists.txt'
- Regex = [regex]'(?<pre>project\s*\([^)]*?\sVERSION\s+)(?<v>[0-9][0-9A-Za-z.\-+]*)'
- Eval = { param($m) $m.Groups['pre'].Value + $Version }
- }
- )
- # --- edit the files ---------------------------------------------------------
- $utf8NoBom = New-Object System.Text.UTF8Encoding($false)
- $results = @()
- foreach ($rule in $rules) {
- $file = Join-Path $root $rule.File
- if (-not (Test-Path -LiteralPath $file -PathType Leaf)) {
- $results += [pscustomobject]@{ File = $rule.File; Status = 'missing'; Old = $null }
- continue
- }
- $content = Get-Content -LiteralPath $file -Raw
- if ($null -eq $content) { $content = '' }
- $m = $rule.Regex.Match($content)
- if (-not $m.Success) {
- $results += [pscustomobject]@{ File = $rule.File; Status = 'no-match'; Old = $null }
- continue
- }
- $old = $m.Groups['v'].Value
- $new = $rule.Regex.Replace($content, [System.Text.RegularExpressions.MatchEvaluator]$rule.Eval, 1)
- if ($new -eq $content) {
- $results += [pscustomobject]@{ File = $rule.File; Status = 'unchanged'; Old = $old }
- continue
- }
- if (-not $DryRun) {
- [System.IO.File]::WriteAllText($file, $new, $utf8NoBom)
- }
- $status = if ($DryRun) { 'would-update' } else { 'updated' }
- $results += [pscustomobject]@{ File = $rule.File; Status = $status; Old = $old }
- }
- # --- report file results ----------------------------------------------------
- Write-Host ''
- Write-Host ("Target version : {0}" -f $Version)
- Write-Host ("Project folder : {0}" -f $root)
- if ($DryRun) { Write-Host 'Mode : DRY RUN (nothing written or pushed)' -ForegroundColor Yellow }
- if ($SkipGit) { Write-Host 'Git : skipped (-SkipGit)' -ForegroundColor DarkGray }
- Write-Host ''
- foreach ($r in $results) {
- switch ($r.Status) {
- 'updated' { Write-Host (" [updated] {0,-20} {1} -> {2}" -f $r.File, $r.Old, $Version) -ForegroundColor Green }
- 'would-update' { Write-Host (" [dry-run] {0,-20} {1} -> {2}" -f $r.File, $r.Old, $Version) -ForegroundColor Cyan }
- 'unchanged' { Write-Host (" [ok] {0,-20} already at {1}" -f $r.File, $Version) -ForegroundColor DarkGray }
- 'no-match' { Write-Host (" [warn] {0,-20} version field not found - left unchanged" -f $r.File) -ForegroundColor Yellow }
- # 'missing' is intentionally silent - a project legitimately may not ship every file.
- }
- }
- # Abort if this doesn't look like a project folder at all (none of the files present).
- $found = @($results | Where-Object { $_.Status -ne 'missing' })
- if ($found.Count -eq 0) {
- Write-Host ''
- Write-Host ("No version files found in {0}." -f $root) -ForegroundColor Red
- Write-Host 'Expected at least one of: library.properties, idf_component.yml, library.json, CMakeLists.txt.' -ForegroundColor Red
- Write-Host 'Nothing was changed and no tag was created - check you are in the right project folder.' -ForegroundColor Red
- exit 1
- }
- $changed = @($results | Where-Object { $_.Status -in 'updated', 'would-update' })
- $warned = @($results | Where-Object { $_.Status -eq 'no-match' })
- $filesToCommit = @($changed | ForEach-Object { $_.File })
- Write-Host ''
- $verb = if ($DryRun) { 'to update' } else { 'updated' }
- Write-Host ("Files: {0} {1}, {2} warning(s)." -f $filesToCommit.Count, $verb, $warned.Count)
- if ($warned.Count -gt 0) {
- Write-Host 'Review the [warn] file(s) - their version field did not match the expected layout.' -ForegroundColor Yellow
- }
- # --- git: commit, tag, push -------------------------------------------------
- if (-not $doGit) { return }
- Write-Host ''
- Write-Host 'Git:'
- if ($DryRun) {
- Write-Host (" git -C `"{0}`" add -A" -f $root) -ForegroundColor Cyan
- Write-Host (" git -C `"{0}`" commit -m `"{1}`" (if the tree has changes)" -f $root, $CommitMessage) -ForegroundColor Cyan
- Write-Host (" git -C `"{0}`" tag {1}" -f $root, $tag) -ForegroundColor Cyan
- Write-Host (" git -C `"{0}`" push {1} HEAD" -f $root, $Remote) -ForegroundColor Cyan
- Write-Host (" git -C `"{0}`" push {1} {2}" -f $root, $Remote, $tag) -ForegroundColor Cyan
- return
- }
- # stage and commit the ENTIRE working tree (new, modified, deleted)
- & git -C $root add -A
- if ($LASTEXITCODE -ne 0) { Write-Error 'git add -A failed.'; exit 1 }
- & git -C $root diff --cached --quiet
- if ($LASTEXITCODE -eq 0) {
- Write-Host ' working tree clean; nothing to commit, tagging current HEAD' -ForegroundColor DarkGray
- } else {
- & git -C $root commit -m $CommitMessage
- if ($LASTEXITCODE -ne 0) { Write-Error 'git commit failed.'; exit 1 }
- Write-Host (" committed all working-tree changes: {0}" -f $CommitMessage) -ForegroundColor Green
- }
- # lightweight tag, name = version verbatim (+ optional prefix)
- & git -C $root tag $tag
- if ($LASTEXITCODE -ne 0) { Write-Error "git tag '$tag' failed."; exit 1 }
- Write-Host (" created tag {0}" -f $tag) -ForegroundColor Green
- # push branch, then tag
- & git -C $root push $Remote HEAD
- if ($LASTEXITCODE -ne 0) { Write-Error 'git push (branch) failed. The commit and tag exist locally but were not pushed.'; exit 1 }
- & git -C $root push $Remote $tag
- if ($LASTEXITCODE -ne 0) { Write-Error "git push (tag '$tag') failed. The branch was pushed; the tag exists locally but was not pushed."; exit 1 }
- Write-Host (" pushed branch + tag '{0}' to {1}" -f $tag, $Remote) -ForegroundColor Green
- Write-Host ''
- Write-Host ("Release {0} complete." -f $Version) -ForegroundColor Green
Advertisement
Add Comment
Please, Sign In to add comment