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
10 changes: 4 additions & 6 deletions .changeset/README.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
# Changesets

This folder is used by [Changesets](https://github.com/changesets/changesets) to manage versioning and changelogs for the codemods in this repository.
This directory contains changesets — short Markdown files that describe changes to packages in this repo.

To add a changeset, run:
When you run `pnpm changeset`, a new file is created here. Commit it with your PR.

```
pnpm changeset
```
When a PR is merged to `main`, the `release.yml` workflow consumes these files, bumps the relevant `package.json` and `codemod.yaml` versions, and opens a **Version Packages** PR. After that PR is merged, the workflow creates git tags and publishes the affected codemods.

Then follow the prompts to select which codemod(s) changed and the type of version bump.
See [Changesets docs](https://github.com/changesets/changesets) for more detail.
11 changes: 9 additions & 2 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -1,2 +1,9 @@
# Auto detect text files and perform LF normalization
* text=auto
* text=auto eol=lf

*.ts diff=javascript
*.tsx diff=javascript
*.js diff=javascript
*.jsx diff=javascript
*.json linguist-language=JSON
*.yaml linguist-language=YAML
*.yml linguist-language=YAML
54 changes: 36 additions & 18 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,15 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/checkout@v4
with:
fetch-depth: 0

- uses: pnpm/action-setup@v4
with:
version: 9.14.2
version: 10.19.0

- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: pnpm
Expand Down Expand Up @@ -91,13 +91,13 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/checkout@v4

- uses: pnpm/action-setup@v4
with:
version: 9.14.2
version: 10.19.0

- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: pnpm
Expand All @@ -112,15 +112,15 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/checkout@v4
with:
fetch-depth: 0

- uses: pnpm/action-setup@v4
with:
version: 9.14.2
version: 10.19.0

- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: pnpm
Expand All @@ -146,8 +146,6 @@ jobs:
d=$parent
done
done
# grep exits 1 when it filters down to zero lines; with `bash -e` that would
# abort before we can treat "no codemod dirs" as success (changeset-only PRs).
changed_dirs=$(printf '%s\n' "$changed_dirs" | sort -u | grep -v '^$' || true)

if [ -z "$changed_dirs" ]; then
Expand Down Expand Up @@ -177,24 +175,42 @@ jobs:
exit 0
fi

# Resolve the whole `.changeset/` tree; a quoted `*.md` pathspec is treated
# literally on many Linux/Git combinations and matches nothing, producing
# false "missing changeset" failures when codemods/ did change.
changeset_files=$(git diff --name-only --diff-filter=ACMR origin/main...HEAD -- .changeset \
| grep '\.md$' \
| grep -v 'README.md' \
|| true)

covered_packages=""
has_empty_changeset=false
for f in $changeset_files; do
pkgs=$(sed -n '/^---$/,/^---$/{ /^---$/d; s/['\''`]//g; s/:.*//; p; }' "$f")
covered_packages="$covered_packages $pkgs"
pkgs=$(echo "$pkgs" | xargs)
if [ -z "$pkgs" ]; then
has_empty_changeset=true
else
covered_packages="$covered_packages $pkgs"
fi
done
covered_packages=$(echo "$covered_packages" | xargs)

# Empty changeset: package.json-only edits do not need a named bump.
if [ "$has_empty_changeset" = true ]; then
filtered=""
for dir in $changed_dirs; do
[ -f "$dir/package.json" ] || continue
pkg=$(node -p "require('./$dir/package.json').name")
other=$(git diff --name-only origin/main...HEAD -- "$dir/" | grep -v 'package.json$' || true)
if [ -n "$other" ]; then
filtered="$filtered $pkg"
else
echo "Empty changeset waives changeset for $pkg (package.json only)."
fi
done
changed_packages=$(echo "$filtered" | xargs)
fi

missing=""
for pkg in $changed_packages; do
# Exact line match: `grep -w` is unreliable for hyphenated npm-style names on GNU grep.
if ! printf '%s\n' $covered_packages | grep -Fxq "$pkg"; then
missing="$missing $pkg"
fi
Expand All @@ -206,7 +222,7 @@ jobs:
exit 0
fi

echo "::error::Missing changeset for: $missing"
echo "::warning::Missing changeset for: $missing"
echo ""
echo "The following packages were changed but not covered by a changeset:"
for pkg in $missing; do
Expand All @@ -217,4 +233,6 @@ jobs:
echo " 1. Run 'pnpm changeset' to add a changeset covering the missing packages"
echo " 2. Add an empty changeset (no packages selected) if no version bump is needed"
echo " 3. Add the 'skip-changeset' label to this PR"
exit 1
echo ""
echo "CI will continue; add a changeset before merge if you plan to release."
exit 0
117 changes: 110 additions & 7 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ name: Publish Codemod (Manual)
on:
workflow_dispatch:
inputs:
codemod_path:
description: 'Path under codemods/ (e.g. apm/nodejs/dd-trace-js/v6/add-link-object-argument)'
tag:
description: 'Tag to publish (format: codemod-name@v1.0.0 or @scopename/codemodname@v1.0.0)'
required: true
type: string

Expand All @@ -14,23 +14,126 @@ permissions:

jobs:
publish:
name: Validate and Publish Codemod
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Harden Runner
uses: step-security/harden-runner@v2
with:
egress-policy: audit

- uses: actions/checkout@v4

- name: Parse tag and extract metadata
id: parse-tag
env:
TAG: ${{ github.event.inputs.tag }}
run: |
# Supports: @scopename/codemodname@v1.0.0 or codemodname@v1.0.0
if [[ ! "$TAG" =~ ^(@[a-zA-Z0-9_-]+/[a-zA-Z0-9_-]+|[a-zA-Z0-9_-]+)@v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo "❌ Invalid tag format: $TAG"
echo "Expected formats:"
echo " - @scopename/codemodname@v1.0.0 (scoped packages)"
echo " - codemodname@v1.0.0 (non-scoped packages)"
exit 1
fi

CODEMOD_NAME="${TAG%@v*}"
VERSION="v${TAG#*@v}"

echo "codemod-name=$CODEMOD_NAME" >> $GITHUB_OUTPUT
echo "version=$VERSION" >> $GITHUB_OUTPUT
echo "✓ Parsed tag — Codemod: $CODEMOD_NAME, Version: $VERSION"

- name: Find codemod directory
id: find-codemod
env:
CODEMOD_NAME: ${{ steps.parse-tag.outputs.codemod-name }}
VERSION: ${{ steps.parse-tag.outputs.version }}
run: |
echo "🔍 Searching for codemod '$CODEMOD_NAME' in all codemod.yaml files..."

CODEMOD_FILES=$(find codemods -name "codemod.yaml" -type f 2>/dev/null || true)

if [[ -z "$CODEMOD_FILES" ]]; then
echo "❌ No codemod.yaml files found in codemods directory"
exit 1
fi

FOUND_PATH=""

while IFS= read -r yaml_file; do
[[ ! -f "$yaml_file" ]] && continue

if command -v yq >/dev/null 2>&1; then
YAML_NAME=$(yq eval '.name' "$yaml_file" 2>/dev/null || echo "")
YAML_VERSION=$(yq eval '.version' "$yaml_file" 2>/dev/null || echo "")
else
YAML_NAME=$(grep -E '^[[:space:]]*name[[:space:]]*:' "$yaml_file" | head -1 \
| sed 's/^[[:space:]]*name[[:space:]]*:[[:space:]]*//' | sed "s/[[:space:]]*$//;s/^[\"']//;s/[\"']$//" || echo "")
YAML_VERSION=$(grep -E '^[[:space:]]*version[[:space:]]*:' "$yaml_file" | head -1 \
| sed 's/^[[:space:]]*version[[:space:]]*:[[:space:]]*//' | sed "s/[[:space:]]*$//;s/^[\"']//;s/[\"']$//" || echo "")
fi

if [[ "$YAML_NAME" == "$CODEMOD_NAME" && "v$YAML_VERSION" == "$VERSION" ]]; then
FOUND_PATH=$(dirname "$yaml_file")
echo "✅ Found codemod at: $FOUND_PATH"
break
fi
done <<< "$CODEMOD_FILES"

if [[ -z "$FOUND_PATH" ]]; then
echo "❌ Codemod '$CODEMOD_NAME' not found at version $VERSION"
echo ""
echo "Available codemods:"
while IFS= read -r yaml_file; do
[[ ! -f "$yaml_file" ]] && continue
if command -v yq >/dev/null 2>&1; then
NAME=$(yq eval '.name' "$yaml_file" 2>/dev/null || echo "unknown")
else
NAME=$(grep -E '^[[:space:]]*name[[:space:]]*:' "$yaml_file" | head -1 \
| sed 's/^[[:space:]]*name[[:space:]]*:[[:space:]]*//' | sed "s/[[:space:]]*$//;s/^[\"']//;s/[\"']$//" || echo "unknown")
fi
echo " - $NAME (in $yaml_file)"
done <<< "$CODEMOD_FILES"
exit 1
fi

echo "codemod-path=$FOUND_PATH" >> $GITHUB_OUTPUT

- uses: pnpm/action-setup@v4
with:
version: 9.14.2
version: 10.19.0

- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: pnpm

- run: pnpm install --frozen-lockfile

- name: Run codemod tests
working-directory: ${{ steps.find-codemod.outputs.codemod-path }}
run: pnpm test

- name: Publish codemod
uses: codemod/publish-action@dd6c8dbc5ceb1a6146feba41481d88b43da50024 # v1
uses: codemod/publish-action@v1
with:
path: codemods/${{ inputs.codemod_path }}
path: ${{ steps.find-codemod.outputs.codemod-path }}

- name: Release summary
env:
TAG: ${{ github.event.inputs.tag }}
CODEMOD_PATH: ${{ steps.find-codemod.outputs.codemod-path }}
ACTOR: ${{ github.triggering_actor }}
run: |
cat >> $GITHUB_STEP_SUMMARY << EOF
# 🚀 Codemod Publication Summary

**Tag:** \`$TAG\`
**Path:** \`$CODEMOD_PATH\`
**Triggered by:** $ACTOR

✅ Codemod has been successfully published to the registry!
EOF
43 changes: 32 additions & 11 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ concurrency: release
permissions:
id-token: write
contents: write
pull-requests: write

jobs:
release:
Expand All @@ -19,16 +20,16 @@ jobs:
outputs:
changed_dirs: ${{ steps.tag.outputs.changed_dirs || '[]' }}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/checkout@v4
with:
fetch-depth: 0
fetch-tags: true

- uses: pnpm/action-setup@v4
with:
version: 9.14.2
version: 10.19.0

- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: pnpm
Expand Down Expand Up @@ -71,14 +72,34 @@ jobs:
git add -A
git commit -m "Version Packages"

- name: Push to main
- name: Open Version Packages pull request
if: steps.diff.outputs.has_changes == 'true'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
git pull --rebase origin main
git push origin main
BRANCH="changeset-release/version-packages"

git push origin HEAD:"refs/heads/${BRANCH}" --force

if gh pr list --base main --head "${BRANCH}" --state open --json number -q '.[0].number' | grep -q .; then
echo "Updated existing Version Packages pull request on branch ${BRANCH}"
else
gh pr create \
--base main \
--head "${BRANCH}" \
--title "Version Packages" \
--body "$(cat <<'EOF'
This PR was opened automatically by the Release workflow.

It applies pending changesets: version bumps in `package.json`, synced `codemod.yaml` versions, and changelog updates.

**After this PR is merged**, the Release workflow will create git tags and publish the affected codemods to the registry.
EOF
)"
fi

- name: Tag released versions
if: steps.diff.outputs.has_changes == 'true'
if: steps.check.outputs.has_changesets != 'true'
id: tag
run: bash scripts/tag-and-publish.sh

Expand All @@ -93,22 +114,22 @@ jobs:
matrix:
dir: ${{ fromJson(needs.release.outputs.changed_dirs) }}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/checkout@v4
with:
ref: main

- uses: pnpm/action-setup@v4
with:
version: 9.14.2
version: 10.19.0

- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: pnpm

- run: pnpm install --frozen-lockfile

- name: Publish codemod
uses: codemod/publish-action@dd6c8dbc5ceb1a6146feba41481d88b43da50024 # v1
uses: codemod/publish-action@v1
with:
path: ${{ matrix.dir }}
Loading
Loading