Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
121 changes: 121 additions & 0 deletions .claude/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
{
"name": "openshift-branching",
"version": "1.0.0",
"description": "Automates OpenShift release pre-branching tasks",
"author": "TestPlatform Team",
"commands": [
{
"name": "pre-branching",
"description": "Execute pre-branching tasks for OpenShift release X.Y",
"usage": "/pre-branching <x.y>",
"settingsFile": "settings.pre-branching.json",
"examples": [
"/pre-branching 4.21",
"/pre-branching 4.22"
]
}
],
"skills": [
{
"name": "create-pre-branching-branch",
"description": "Create and checkout pre-branching branch in release repository",
"script": "skills/create-pre-branching-branch.sh",
"permissions": [
"Bash(git fetch:*)",
"Bash(git pull:*)",
"Bash(git checkout:*)",
"Bash(git show-ref:*)"
]
},
{
"name": "download-sippy-config",
"description": "Download latest Sippy configuration from GitHub",
"script": "skills/download-sippy-config.sh",
"permissions": [
"Bash(curl :*)",
"Write(/tmp/**)"
]
},
{
"name": "copy-release-controller-configs",
"description": "Copy and update release-controller configurations from old version to new version",
"script": "skills/copy-release-controller-configs.sh",
"permissions": [
"Read(../release/**)",
"Edit(../release/**)",
"Bash(cp :*)",
"Bash(sed :*)",
"Bash(find :*)"
]
},
{
"name": "update-release-gating-jobs",
"description": "Run generated-release-gating-jobs bumper with Sippy config",
"script": "skills/update-release-gating-jobs.sh",
"permissions": [
"Bash(go build:*)",
"Bash(./generated-release-gating-jobs :*)",
"Read(../release/**)",
"Edit(../release/**)"
]
},
{
"name": "copy-handcrafted-release-gating-jobs",
"description": "Populate handcrafted X.Y+1 release-gating jobs from X.Y release",
"script": "skills/copy-handcrafted-release-gating-jobs.sh",
"permissions": [
"Bash(cp :*)",
"Bash(sed :*)",
"Bash(make release-controllers)",
"Bash(make jobs)",
"Read(../release/**)",
"Edit(../release/**)"
]
},
{
"name": "regenerate-prow-jobs",
"description": "Regenerate Prow job configurations from CI operator configs",
"script": "skills/regenerate-prow-jobs.sh",
"permissions": [
"Bash(make jobs)",
"Read(../release/**)",
"Write(../release/**)"
]
},
{
"name": "validate-release-controller-config",
"description": "Validate release-controller configurations",
"script": "skills/validate-release-controller-config.sh",
"permissions": [
"Bash(hack/validate-release-controller-config.sh :*)",
"Read(../release/**)"
]
},
{
"name": "bump-analysis-jobs",
"description": "Bump analysis jobs (install, upgrade, overall) from old version to new version",
"script": "skills/bump-analysis-jobs.sh",
"permissions": [
"Bash(python3:*)",
"Read(../release/**)",
"Edit(../release/**)"
]
},
{
"name": "create-pre-branching-commits",
"description": "Create the three standard pre-branching commits",
"script": "skills/create-pre-branching-commits.sh",
"permissions": [
"Bash(git add:*)",
"Bash(git commit:*)",
"Bash(git diff:*)",
"Bash(git log:*)",
"Bash(git ls-files:*)"
]
}
],
"config": {
"RELEASE_REPO": "/home/prucek/work/release",
"CI_TOOLS_REPO": "/home/prucek/work/ci-tools"
Comment on lines +118 to +119
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Replace hardcoded developer paths with environment variables or relative paths.

Lines 105-106 contain absolute paths specific to the developer's machine (/home/prucek/work/...). These will not work for other developers or CI environments and appear to be accidentally committed developer config.

Use environment variables or relative paths instead. For example:

- "config": {
-   "RELEASE_REPO": "/home/prucek/work/release",
-   "CI_TOOLS_REPO": "/home/prucek/work/ci-tools"
- }
+ "config": {
+   "RELEASE_REPO": "${RELEASE_REPO:-../release}",
+   "CI_TOOLS_REPO": "${CI_TOOLS_REPO:-../ci-tools}"
+ }

Alternatively, document in README how users should configure these paths before running the plugin, or derive them from the current working directory.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In .claude/.claude-plugin/plugin.json around lines 105-106 there are hardcoded
absolute developer paths (/home/prucek/work/release and
/home/prucek/work/ci-tools); replace these with configurable values by reading
environment variables (e.g., RELEASE_REPO and CI_TOOLS_REPO) or using paths
relative to the repository root (e.g., ./release, ./ci-tools) and provide
sensible fallbacks if unset; update the plugin to load those env vars or
construct relative paths at runtime and add instructions to the README (or a
README section) describing how to set the environment variables or where to
place the directories so CI and other developers don’t rely on machine-specific
paths.

}
}
114 changes: 114 additions & 0 deletions .claude/.claude-plugin/skills/bump-analysis-jobs.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
#!/bin/bash
set -euo pipefail

OLD_VERSION="${1:-}"
NEW_VERSION="${2:-}"
RELEASE_REPO="${3:-}"

if [ -z "$OLD_VERSION" ] || [ -z "$NEW_VERSION" ] || [ -z "$RELEASE_REPO" ]; then
echo "Usage: $0 <old_version> <new_version> <release_repo>"
echo "Example: $0 4.21 4.22 /path/to/release"
exit 1
fi

echo "=========================================="
echo "Bumping Analysis Jobs"
echo "=========================================="
echo ""
echo "Old version: $OLD_VERSION"
echo "New version: $NEW_VERSION"
echo "Release repo: $RELEASE_REPO"
echo ""

cd "$RELEASE_REPO"

# Export variables for Python script
export OLD_VERSION
export NEW_VERSION

# Python script to extract and bump jobs
python3 << 'PYTHON_EOF'
import re
import sys
import os

old_version = os.environ['OLD_VERSION']
new_version = os.environ['NEW_VERSION']

def bump_analysis_job(job_text, old_ver, new_ver):
"""Replace version numbers in a job definition"""
return job_text.replace(old_ver, new_ver)
Comment on lines +38 to +40
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Replace naive string replacement with semantic version bumping.

The bump_analysis_job function uses string.replace() to swap old and new versions (line 40), which will replace all occurrences of the old_version throughout the entire job block. This risks corrupting job definitions if the version string appears in unintended contexts (e.g., image URIs, configuration values, comments).

Use targeted regex replacement that only updates version strings in specific YAML fields. For example:

 def bump_analysis_job(job_text, old_ver, new_ver):
-    """Replace version numbers in a job definition"""
-    return job_text.replace(old_ver, new_ver)
+    """Replace version numbers in specific YAML fields"""
+    # Only replace version in predictable fields (name, image tag refs, etc.)
+    # Avoid replacing in unrelated config or comments
+    result = job_text
+    # Replace in job name field
+    result = re.sub(
+        rf'(\n  name:.*?{re.escape(old_ver)})',
+        lambda m: m.group(1).replace(old_ver, new_ver),
+        result
+    )
+    # Add similar targeted replacements for other fields as needed
+    return result

Committable suggestion skipped: line range outside the PR's diff.


def extract_jobs(file_path, job_names):
"""Extract complete job definitions for given job names from a YAML file"""
with open(file_path, 'r') as f:
content = f.read()

jobs = []
for job_name in job_names:
# Find the job by name - match from "- " to the next "- " or end of file
pattern = rf'(\n- .*?\n name: {re.escape(job_name)}\n.*?)(?=\n- |\Z)'
match = re.search(pattern, content, re.DOTALL)
if match:
jobs.append(match.group(1))
print(f" ✓ Found job: {job_name}", file=sys.stderr)
else:
print(f" ✗ Could not find job: {job_name}", file=sys.stderr)

return jobs

def append_bumped_jobs(file_path, job_names, old_ver, new_ver):
"""Extract jobs, bump versions, and append to the file"""
print(f"\nProcessing {file_path}...", file=sys.stderr)
jobs = extract_jobs(file_path, job_names)

if not jobs:
print(f" No jobs found in {file_path}", file=sys.stderr)
return False

# Read the current file
with open(file_path, 'r') as f:
content = f.read()

# Bump versions in the extracted jobs
bumped_jobs = [bump_analysis_job(job, old_ver, new_ver) for job in jobs]

# Append the bumped jobs to the file
new_content = content.rstrip() + '\n' + '\n'.join(bumped_jobs) + '\n'

with open(file_path, 'w') as f:
f.write(new_content)

print(f" ✓ Added {len(bumped_jobs)} bumped jobs", file=sys.stderr)
return True
Comment on lines +60 to +83
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Add idempotency guards to prevent duplicate job entries on repeated runs.

The append_bumped_jobs function unconditionally appends bumped jobs to the YAML file (line 77) without checking if they already exist. Running this script multiple times will create duplicate job entries, corrupting the configuration.

Modify the function to check if the bumped jobs already exist in the file before appending. For example:

 def append_bumped_jobs(file_path, job_names, old_ver, new_ver):
     """Extract jobs, bump versions, and append to the file"""
     print(f"\nProcessing {file_path}...", file=sys.stderr)
     jobs = extract_jobs(file_path, job_names)
 
     if not jobs:
         print(f"  No jobs found in {file_path}", file=sys.stderr)
         return False
 
     # Read the current file
     with open(file_path, 'r') as f:
         content = f.read()
 
+    # Bump versions in the extracted jobs
+    bumped_jobs = [bump_analysis_job(job, old_ver, new_ver) for job in jobs]
+
+    # Check if bumped jobs already exist in file to avoid duplicates
+    existing_jobs = '\n'.join(bumped_jobs) in content
+    if existing_jobs:
+        print(f"  ℹ Bumped jobs already exist in {file_path}, skipping append", file=sys.stderr)
+        return True
+
     # Bump versions in the extracted jobs
-    bumped_jobs = [bump_analysis_job(job, old_ver, new_ver) for job in jobs]
 
     # Append the bumped jobs to the file
     new_content = content.rstrip() + '\n' + '\n'.join(bumped_jobs) + '\n'

Alternatively, replace entire job entries instead of appending, or use a more robust YAML library that maintains referential integrity.

🤖 Prompt for AI Agents
In .claude/.claude-plugin/skills/bump-analysis-jobs.sh around lines 60–83, the
function append_bumped_jobs currently unconditionally appends bumped job YAML
fragments which causes duplicate job entries on repeated runs; update it to be
idempotent by detecting existing bumped jobs and either skipping or replacing
them before writing. Specifically, parse the current file (prefer using a YAML
loader) to identify jobs by their unique key/name/ID, for each bumped job check
if an entry with the same key already exists and if so replace that entry (or
skip appending if identical), otherwise append the new job; only write the
merged/updated YAML back to disk so repeated runs do not create duplicates.


# Bump multiarch jobs
multiarch_file = 'ci-operator/jobs/openshift/multiarch/openshift-multiarch-master-periodics.yaml'
multiarch_jobs = [
f'periodic-ci-openshift-multiarch-master-nightly-{old_version}-install-analysis-all-multi-p-p',
f'periodic-ci-openshift-multiarch-master-nightly-{old_version}-install-analysis-all-ppc64le',
f'periodic-ci-openshift-multiarch-master-nightly-{old_version}-install-analysis-all-s390x',
]

print("\n=== Multiarch Analysis Jobs ===", file=sys.stderr)
append_bumped_jobs(multiarch_file, multiarch_jobs, old_version, new_version)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Check return values from append_bumped_jobs to detect failures.

Lines 94 and 105 call append_bumped_jobs but ignore its return value. If jobs are not found in a file, the function returns False, but the script continues and still exits with status 0, masking the failure.

Capture and handle the return value:

 print("\n=== Multiarch Analysis Jobs ===", file=sys.stderr)
-append_bumped_jobs(multiarch_file, multiarch_jobs, old_version, new_version)
+if not append_bumped_jobs(multiarch_file, multiarch_jobs, old_version, new_version):
+    print("Warning: No multiarch jobs were processed", file=sys.stderr)
 
 # Bump release jobs
 release_file = 'ci-operator/jobs/openshift/release/openshift-release-master-periodics.yaml'
@@ -102,7 +102,9 @@
 ]
 
 print("\n=== Release Analysis Jobs ===", file=sys.stderr)
-append_bumped_jobs(release_file, release_jobs, old_version, new_version)
+if not append_bumped_jobs(release_file, release_jobs, old_version, new_version):
+    print("Error: No release jobs were processed", file=sys.stderr)
+    sys.exit(1)

Consider whether missing jobs should be warnings (non-fatal) or errors (fatal) based on your workflow expectations.

Also applies to: 105-105

🤖 Prompt for AI Agents
.claude/.claude-plugin/skills/bump-analysis-jobs.sh around lines 94 and 105: the
script calls append_bumped_jobs(...) but ignores its boolean return value, so
failures (returns False when jobs not found) are masked; update both call sites
to capture the return value, check it, and handle failures according to policy —
either emit a warning and continue or log an error and exit with a non-zero
status (prefer exiting if missing jobs should be fatal); ensure the log message
includes which file and versions failed and return/exit appropriately so the
script surface failures instead of exiting 0.


# Bump release jobs
release_file = 'ci-operator/jobs/openshift/release/openshift-release-master-periodics.yaml'
Comment on lines +86 to +97
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Add file existence checks before read/write operations.

The script assumes both YAML files exist at lines 86 and 97 but does not validate their presence. If a file is missing, the script will fail with a cryptic Python traceback instead of a clear error message.

Add existence checks before processing:

+ # Verify files exist
+ multiarch_file = 'ci-operator/jobs/openshift/multiarch/openshift-multiarch-master-periodics.yaml'
+ release_file = 'ci-operator/jobs/openshift/release/openshift-release-master-periodics.yaml'
+ 
+ if [ ! -f "$multiarch_file" ]; then
+     echo "Error: Multiarch jobs file not found: $multiarch_file" >&2
+     exit 1
+ fi
+ if [ ! -f "$release_file" ]; then
+     echo "Error: Release jobs file not found: $release_file" >&2
+     exit 1
+ fi

Or add validation in Python before opening files.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In .claude/.claude-plugin/skills/bump-analysis-jobs.sh around lines 86 to 97 the
script calls append_bumped_jobs on YAML paths without checking the files exist;
add explicit existence checks for multiarch_file and release_file before any
read/write operations and fail fast with a clear error message if either is
missing. Implement this by testing for file presence (e.g., os.path.exists or
shell test -f depending on runtime) prior to calling append_bumped_jobs, and
return or exit with a descriptive error that includes the missing path so
subsequent file reads/writes won’t produce a cryptic traceback.

release_jobs = [
f'periodic-ci-openshift-release-master-nightly-{old_version}-install-analysis-all',
f'periodic-ci-openshift-release-master-nightly-{old_version}-upgrade-analysis-all',
f'periodic-ci-openshift-release-master-nightly-{old_version}-overall-analysis-all',
]

print("\n=== Release Analysis Jobs ===", file=sys.stderr)
append_bumped_jobs(release_file, release_jobs, old_version, new_version)

print("\n✓ All analysis jobs bumped successfully", file=sys.stderr)

PYTHON_EOF

echo ""
echo "=========================================="
echo "✓ Analysis jobs bumped!"
echo "=========================================="
110 changes: 110 additions & 0 deletions .claude/.claude-plugin/skills/copy-handcrafted-release-gating-jobs.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
#!/bin/bash
# Skill: copy-handcrafted-release-gating-jobs
# Description: Populate handcrafted X.Y+1 release-gating jobs from X.Y release
# Usage: copy-handcrafted-release-gating-jobs.sh <old_version> <new_version> <release_repo_path>

set -euo pipefail

if [ $# -ne 3 ]; then
echo "Usage: $0 <old_version> <new_version> <release_repo_path>"
echo "Example: $0 4.21 4.22 /home/prucek/work/release"
exit 1
fi

OLD_VERSION="$1"
NEW_VERSION="$2"
RELEASE_REPO="$3"
JOBS_DIR="${RELEASE_REPO}/ci-operator/jobs/openshift/release"
REPOS_DIR="${RELEASE_REPO}/core-services/release-controller/_repos"

echo "=========================================="
echo "Copying handcrafted release-gating jobs"
echo "Version: ${OLD_VERSION} → ${NEW_VERSION}"
echo "=========================================="
echo ""

# Check prerequisites
echo "Checking prerequisites..."

# Check if release-controller repo files exist for new version
REPO_FILES=$(find "${REPOS_DIR}" -type f -name "ocp-${NEW_VERSION}*.repo" 2>/dev/null | wc -l)

if [ "$REPO_FILES" -eq 0 ]; then
echo ""
echo "ERROR: No ocp-${NEW_VERSION}*.repo files found in ${REPOS_DIR}"
echo ""
echo "You must first execute Step 3 (Release-Controller Configurations):"
echo " 1. Run the 'copy-release-controller-configs' skill"
echo " 2. Ensure core-services/release-controller/_repos/ocp-${NEW_VERSION}*.repo files exist"
echo ""
exit 1
fi

echo "✓ Found ${REPO_FILES} ocp-${NEW_VERSION}*.repo file(s)"
echo ""

# Source and destination file paths
SRC_FILE="${JOBS_DIR}/openshift-release-release-${OLD_VERSION}-periodics.yaml"
DST_FILE="${JOBS_DIR}/openshift-release-release-${NEW_VERSION}-periodics.yaml"

# Check if source file exists
if [ ! -f "$SRC_FILE" ]; then
echo "ERROR: Source file not found: $SRC_FILE"
exit 1
fi

# Check if destination already exists
if [ -f "$DST_FILE" ]; then
echo "WARNING: Destination file already exists: $DST_FILE"
echo "Skipping copy operation."
else
echo "Copying: $(basename "$SRC_FILE") → $(basename "$DST_FILE")"
cp "$SRC_FILE" "$DST_FILE"
echo "✓ File copied successfully"
echo ""
fi

# Bump ALL version strings inside the file
echo "Bumping all version strings in $(basename "$DST_FILE")..."

# Extract the major version (e.g., "4" from "4.21")
MAJOR_VERSION="${OLD_VERSION%%.*}"

# Find all unique version numbers in the file and sort them in descending order
# This ensures we replace from highest to lowest to avoid double-replacement
VERSIONS=$(grep -oE "${MAJOR_VERSION}\.[0-9]+" "$DST_FILE" | sort -t. -k2 -nr | uniq)

# Bump each version found in the file
for VERSION in $VERSIONS; do
# Extract minor version
MINOR="${VERSION##*.}"
# Calculate new minor version
NEW_MINOR=$((MINOR + 1))
NEW_VER="${MAJOR_VERSION}.${NEW_MINOR}"

# Escape dots for sed
VERSION_ESCAPED="${VERSION//./\\.}"

echo " Bumping ${VERSION} → ${NEW_VER}"
sed -i "s/${VERSION_ESCAPED}/${NEW_VER}/g" "$DST_FILE"
done

echo "✓ All version strings updated"
echo ""

# Run make release-controllers
echo "Running 'make release-controllers'..."
cd "${RELEASE_REPO}"
make release-controllers
echo "✓ Release controllers generated"
echo ""

# Run make jobs
echo "Running 'make jobs'..."
make jobs
echo "✓ Prow jobs regenerated"
echo ""

echo "=========================================="
echo "✓ Handcrafted release-gating jobs ready!"
echo "=========================================="
45 changes: 45 additions & 0 deletions .claude/.claude-plugin/skills/copy-release-controller-configs.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
#!/bin/bash
# Skill: copy-release-controller-configs
# Description: Copy and update release-controller configurations from old version to new version
# Usage: copy-release-controller-configs.sh <old_version> <new_version> <release_repo_path>

set -euo pipefail

if [ $# -ne 3 ]; then
echo "Usage: $0 <old_version> <new_version> <release_repo_path>"
echo "Example: $0 4.21 4.22 /home/prucek/work/release"
exit 1
fi

OLD_VERSION="$1"
NEW_VERSION="$2"
RELEASE_REPO="$3"
RELEASES_DIR="${RELEASE_REPO}/core-services/release-controller/_releases"

echo "Bumping release controller configs: ${OLD_VERSION} → ${NEW_VERSION}"

# Escape dots for sed patterns
OLD_ESCAPED="${OLD_VERSION//./\\.}"

# Calculate previous version for nested references
PREV_MINOR=$((${OLD_VERSION##*.} - 1))
PREV_VERSION="${OLD_VERSION%.*}.${PREV_MINOR}"
PREV_ESCAPED="${PREV_VERSION//./\\.}"
CURR_MINOR=$((${NEW_VERSION##*.} - 1))
CURR_VERSION="${NEW_VERSION%.*}.${CURR_MINOR}"

# Find all files with old_version in their name and bump them
find "$RELEASES_DIR" -type f -name "*${OLD_VERSION}*.json" 2>/dev/null | while read -r src_file; do
# Skip if already processed/destination exists
dst_file="${src_file//${OLD_VERSION}/${NEW_VERSION}}"
[ -f "$dst_file" ] && continue

echo " $(basename "$src_file") → $(basename "$dst_file")"
cp "$src_file" "$dst_file"

# Bump version strings inside the file
sed -i "s/${OLD_ESCAPED}/${NEW_VERSION}/g" "$dst_file"
sed -i "s/${PREV_ESCAPED}/${CURR_VERSION}/g" "$dst_file"
Comment on lines +14 to +42
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Address fragile version parsing and sed backup risk.

Multiple issues in this section:

  1. Version parsing assumes X.Y format (Lines 25-28): The arithmetic PREV_MINOR=$((${OLD_VERSION##*.} - 1)) will fail or produce unexpected results for versions like "4" (no dot), "4.21.1" (too many dots), or when subtracting results in negative numbers. For example, version "4.0" would produce PREV_MINOR = -1.

  2. sed -i without backup (Line 41-42): In-place sed edits without backup (with -i .bak or similar) risk data corruption if sed fails. The script uses set -euo pipefail but sed errors within the file loop won't necessarily stop execution.

  3. Filename pattern matching could be too broad (Line 32): Files matching *${OLD_VERSION}*.json could unintentionally match unrelated files. For example, version "4.2" would match "4.21" files.

Consider this improved version:

-# Calculate previous version for nested references
-PREV_MINOR=$((${OLD_VERSION##*.} - 1))
-PREV_VERSION="${OLD_VERSION%.*}.${PREV_MINOR}"
+# Validate version format and calculate previous version
+if [[ ! "$OLD_VERSION" =~ ^[0-9]+\.[0-9]+$ ]]; then
+  echo "✗ Error: OLD_VERSION must be in X.Y format (e.g., 4.21)"
+  exit 1
+fi
+
+MAJOR="${OLD_VERSION%.*}"
+MINOR="${OLD_VERSION##*.}"
+PREV_MINOR=$((MINOR - 1))
+if [ "$PREV_MINOR" -lt 0 ]; then
+  echo "✗ Error: Cannot calculate previous version for ${OLD_VERSION}"
+  exit 1
+fi
+PREV_VERSION="${MAJOR}.${PREV_MINOR}"

-find "$RELEASES_DIR" -type f -name "*${OLD_VERSION}*.json" 2>/dev/null | while read -r src_file; do
+# Use word-boundary pattern to avoid partial matches
+find "$RELEASES_DIR" -type f -name "*-${OLD_VERSION}.json" 2>/dev/null | while read -r src_file; do
     dst_file="${src_file//${OLD_VERSION}/${NEW_VERSION}}"
     [ -f "$dst_file" ] && continue
 
     echo "  $(basename "$src_file")$(basename "$dst_file")"
     cp "$src_file" "$dst_file"
 
     # Bump version strings inside the file
-    sed -i "s/${OLD_ESCAPED}/${NEW_VERSION}/g" "$dst_file"
-    sed -i "s/${PREV_ESCAPED}/${CURR_VERSION}/g" "$dst_file"
+    sed -i.bak "s/${OLD_ESCAPED}/${NEW_VERSION}/g" "$dst_file" && rm -f "${dst_file}.bak"
+    sed -i.bak "s/${PREV_ESCAPED}/${CURR_VERSION}/g" "$dst_file" && rm -f "${dst_file}.bak"
done

Also validate that RELEASES_DIR exists at line 17.

done

echo "Done!"
Loading