Skip to content
Merged
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
10 changes: 7 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,13 @@ jobs:
- name: Get changed codemods
id: codemods
run: |
dirs=$(git diff --name-only origin/main...HEAD -- 'codemods/' \
| awk -F/ 'NF>=3 {print $1"/"$2"/"$3}' | sort -u | tr '\n' ' ')
echo "dirs=$dirs" >> "$GITHUB_OUTPUT"
candidates=$(git diff --name-only origin/main...HEAD -- 'codemods/' \
| awk -F/ 'NF>=3 {print $1"/"$2"/"$3}' | sort -u)
dirs=""
for d in $candidates; do
[ -f "$d/package.json" ] && dirs="$dirs $d"
done
echo "dirs=$(echo $dirs | xargs)" >> "$GITHUB_OUTPUT"

- name: Format check (changed files)
if: steps.changed.outputs.files != ''
Expand Down
6 changes: 4 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,17 +70,19 @@ The manual `Publish Codemod (Manual)` workflow (`.github/workflows/publish.yml`)

## Adding a new codemod

Each codemod lives in its own directory under `codemods/<version>/<codemod-name>/`:
Each codemod lives in its own directory under `codemods/<group>/<codemod-name>/`:

```
codemods/<version>/<codemod-name>/
codemods/<group>/<codemod-name>/
scripts/codemod.ts # Codemod logic (jssg / ast-grep)
tests/ # Input/expected test fixtures
codemod.yaml # Codemod manifest (version is auto-synced)
workflow.yaml # Execution workflow
package.json # Source of truth for name + version
```

The `<group>` is either a Backstage release version (e.g. `v1.52.0`) for migration codemods, or `misc` for codemods not tied to a specific release (e.g. NFS migration).

Conventions to follow:

- The two-level glob `codemods/*/*/` is assumed by `scripts/sync-codemod-versions.sh` and `scripts/tag-and-publish.sh`. Don't flatten or deepen this layout.
Expand Down
Empty file added codemods/misc/.gitkeep
Empty file.
51 changes: 35 additions & 16 deletions scripts/generate-readme-codemods.sh
Original file line number Diff line number Diff line change
Expand Up @@ -16,20 +16,25 @@ CODEMODS_DIR="$REPO_ROOT/codemods"
START_MARKER="<!-- CODEMODS_START -->"
END_MARKER="<!-- CODEMODS_END -->"

# Render a version table
render_version() {
local version="$1"
local version_dir="$CODEMODS_DIR/$version"
# Render a table for a group of codemods
render_group() {
local group="$1"
local group_dir="$CODEMODS_DIR/$group"

echo "### $version"
echo ""
# shellcheck disable=SC2016
echo "Run the [\`migration-recipe\`](./codemods/$version/migration-recipe) to apply every codemod below in one pass, or run any individual codemod on its own."
echo "### $group"
echo ""

# Only show migration-recipe link for version directories that have one
if [ -d "$group_dir/migration-recipe" ]; then
# shellcheck disable=SC2016
echo "Run the [\`migration-recipe\`](./codemods/$group/migration-recipe) to apply every codemod below in one pass, or run any individual codemod on its own."
echo ""
fi

echo "| Codemod | Description |"
echo "| ------- | ----------- |"

for codemod_dir in "$version_dir"/*/; do
for codemod_dir in "$group_dir"/*/; do
local name
name=$(basename "$codemod_dir")
local yaml="$codemod_dir/codemod.yaml"
Expand All @@ -40,31 +45,45 @@ render_version() {
# Strip "Backstage X.Y.Z: " prefix — the version header already shows it
desc=$(echo "$desc" | sed 's/^Backstage [0-9.]*: *//')

echo "| [$name](./codemods/$version/$name) | $desc |"
echo "| [$name](./codemods/$group/$name) | $desc |"
done

echo ""
}

# Collect all versions (newest first), show only latest 2
mapfile -t all_versions < <(ls "$CODEMODS_DIR" | sort -Vr)
total=${#all_versions[@]}
# Separate version directories (v*) from non-version groups (misc, etc.)
mapfile -t version_dirs < <(ls "$CODEMODS_DIR" | grep '^v' | sort -Vr)
mapfile -t other_dirs < <(ls "$CODEMODS_DIR" | grep -v '^v' | grep -v '^\.gitkeep$' | sort)

# Show latest 2 versions
total=${#version_dirs[@]}
show=2
if [ "$total" -lt "$show" ]; then
show=$total
fi

# Build the section
section=""
for version in "${all_versions[@]:0:$show}"; do
section+=$(render_version "$version")
for version in "${version_dirs[@]:0:$show}"; do
section+=$(render_group "$version")
section+=$'\n'
done

if [ "$total" -gt "$show" ]; then
section+="Older versions are available in the [\`codemods/\`](./codemods) directory."$'\n\n'
fi

# Append non-version groups (misc, etc.) if they contain any codemods
for group in "${other_dirs[@]}"; do
local_dir="$CODEMODS_DIR/$group"
# Skip empty groups (only .gitkeep) — count subdirectories with codemod.yaml
codemod_count=$(find "$local_dir" -mindepth 2 -maxdepth 2 -name 'codemod.yaml' 2>/dev/null | head -1 || true)
if [ -n "$codemod_count" ]; then
section+=$(render_group "$group")
section+=$'\n'
fi
done

# Check markers exist
if ! grep -q "$START_MARKER" "$README"; then
echo "ERROR: $START_MARKER not found in README.md"
Expand All @@ -86,4 +105,4 @@ mv "$README.tmp" "$README"
# Format so the committed output matches what prettier expects
yarn format "$README" >/dev/null 2>&1 || true

echo "✅ README.md updated with ${all_versions[0]} and ${all_versions[1]} ($total total on disk)"
echo "✅ README.md updated with ${version_dirs[0]} and ${version_dirs[1]} ($total versions on disk)"