From 2bdb70121fe07fd54bd0ce34836950820d2d065e Mon Sep 17 00:00:00 2001 From: raj pandey Date: Tue, 28 Apr 2026 17:36:58 +0530 Subject: [PATCH 1/4] fix: Modified github workflows for release and remove check-branch workflow --- .github/workflows/back-merge-pr.yml | 59 +++++++++++++++++ .github/workflows/check-branch.yml | 20 ------ .github/workflows/check-version-bump.yml | 82 ++++++++++++++++++++++++ .github/workflows/ci.yml | 4 +- .github/workflows/npm-publish.yml | 7 +- AGENTS.md | 2 +- CONTRIBUTING.md | 4 +- skills/dev-workflow/SKILL.md | 6 +- 8 files changed, 153 insertions(+), 31 deletions(-) create mode 100644 .github/workflows/back-merge-pr.yml delete mode 100644 .github/workflows/check-branch.yml create mode 100644 .github/workflows/check-version-bump.yml diff --git a/.github/workflows/back-merge-pr.yml b/.github/workflows/back-merge-pr.yml new file mode 100644 index 0000000..cec0f26 --- /dev/null +++ b/.github/workflows/back-merge-pr.yml @@ -0,0 +1,59 @@ +# Opens a PR from master → development after changes land on master (back-merge). +# +# Org/repo Settings → Actions → General → Workflow permissions: read and write +# (so GITHUB_TOKEN can create pull requests). Or use a PAT in secret GH_TOKEN. + +name: Back-merge master to development + +on: + push: + branches: [master] + workflow_dispatch: + +permissions: + contents: read + pull-requests: write + +jobs: + open-back-merge-pr: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Open back-merge PR if needed + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + set -euo pipefail + git fetch origin development master + + MASTER_SHA=$(git rev-parse origin/master) + DEV_SHA=$(git rev-parse origin/development) + + if [ "$MASTER_SHA" = "$DEV_SHA" ]; then + echo "master and development are at the same commit; nothing to back-merge." + exit 0 + fi + + EXISTING=$(gh pr list --repo "${{ github.repository }}" \ + --base development \ + --head master \ + --state open \ + --json number \ + --jq 'length') + + if [ "$EXISTING" -gt 0 ]; then + echo "An open PR from master to development already exists; skipping." + exit 0 + fi + + gh pr create --repo "${{ github.repository }}" \ + --base development \ + --head master \ + --title "chore: back-merge master into development" \ + --body "Automated back-merge after changes landed on \`master\`. Review and merge to keep \`development\` in sync." + + echo "Created back-merge PR master → development." diff --git a/.github/workflows/check-branch.yml b/.github/workflows/check-branch.yml deleted file mode 100644 index c63e084..0000000 --- a/.github/workflows/check-branch.yml +++ /dev/null @@ -1,20 +0,0 @@ -name: 'Check Branch' - -on: - pull_request: - -jobs: - check_branch: - runs-on: ubuntu-latest - steps: - - name: Comment PR - if: github.base_ref == 'staging' && github.head_ref != 'development' - uses: thollander/actions-comment-pull-request@v2 - with: - message: | - We regret to inform you that you are currently not able to merge your changes into the master branch due to restrictions applied by our SRE team. To proceed with merging your changes, we kindly request that you create a pull request from the development branch. Our team will then review the changes and work with you to ensure a successful merge into the master branch. - - name: Check branch - if: github.base_ref == 'staging' && github.head_ref != 'development' - run: | - echo "ERROR: We regret to inform you that you are currently not able to merge your changes into the master branch due to restrictions applied by our SRE team. To proceed with merging your changes, we kindly request that you create a pull request from the development branch. Our team will then review the changes and work with you to ensure a successful merge into the master branch." - exit 1 \ No newline at end of file diff --git a/.github/workflows/check-version-bump.yml b/.github/workflows/check-version-bump.yml new file mode 100644 index 0000000..620af85 --- /dev/null +++ b/.github/workflows/check-version-bump.yml @@ -0,0 +1,82 @@ +name: Check Version Bump + +on: + pull_request: + branches: [main, master] + +jobs: + check-version-bump: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Validate version and changelog updates + shell: bash + run: | + set -euo pipefail + + VERSION_FILE="package.json" + CHANGELOG_FILE="CHANGELOG.md" + BASE_SHA="${{ github.event.pull_request.base.sha }}" + HEAD_SHA="${{ github.event.pull_request.head.sha }}" + + mapfile -t CHANGED_FILES < <(git diff --name-only "$BASE_SHA" "$HEAD_SHA") + if [ "${#CHANGED_FILES[@]}" -eq 0 ]; then + echo "No changed files detected." + exit 0 + fi + + is_ignored_change() { + local f="$1" + [[ "$f" =~ ^docs/ ]] && return 0 + [[ "$f" =~ ^\.github/ ]] && return 0 + [[ "$f" =~ (^|/)tests?/ ]] && return 0 + [[ "$f" =~ (^|/)__tests__/ ]] && return 0 + [[ "$f" =~ \.md$ ]] && [[ ! "$f" =~ (^|/)CHANGELOG\.md$ ]] && return 0 + return 1 + } + + has_release_impact=false + for file in "${CHANGED_FILES[@]}"; do + if ! is_ignored_change "$file"; then + has_release_impact=true + break + fi + done + + if [ "$has_release_impact" = false ]; then + echo "Skipping docs/test-only PR." + exit 0 + fi + + changed_file() { + local target="$1" + for file in "${CHANGED_FILES[@]}"; do + if [ "$file" = "$target" ]; then + return 0 + fi + done + return 1 + } + + changed_file "$VERSION_FILE" || { echo "Version bump required in $VERSION_FILE."; exit 1; } + changed_file "$CHANGELOG_FILE" || { echo "Matching changelog update required in $CHANGELOG_FILE."; exit 1; } + + head_version=$(node -e "console.log(require('./package.json').version)") + CHANGELOG_HEAD=$(sed -nE 's/^## v?([^[:space:]]+).*/\1/p' "$CHANGELOG_FILE" | head -1) + + [ -n "$CHANGELOG_HEAD" ] || { echo "::error::Could not find a top changelog heading like '## vX.Y.Z' in $CHANGELOG_FILE."; exit 1; } + [ "$CHANGELOG_HEAD" = "$head_version" ] || { echo "::error::$CHANGELOG_FILE top version ($CHANGELOG_HEAD) does not match project version ($head_version)."; exit 1; } + + base_version=$(git show "$BASE_SHA:$VERSION_FILE" | node -e "let d=''; process.stdin.on('data', c => d += c); process.stdin.on('end', () => console.log(JSON.parse(d).version));") + latest_tag=$(git tag --list 'v*' --sort=-version:refname | sed -n '1p') + latest_version="${latest_tag#v}" + [ -n "$latest_version" ] || latest_version="0.0.0" + + version_gt() { + python3 -c 'import sys;v=lambda s:[int(x) if x.isdigit() else 0 for x in (s.strip().lstrip("v").split("-",1)[0].split("+",1)[0].split(".")+["0","0","0"])[:3]];print("true" if v(sys.argv[1])>v(sys.argv[2]) else "false")' "$1" "$2" + } + + [ "$(version_gt "$head_version" "$base_version")" = "true" ] || { echo "Version must be greater than base version ($base_version). Found $head_version."; exit 1; } + [ "$(version_gt "$head_version" "$latest_version")" = "true" ] || { echo "Version must be greater than latest tag version ($latest_version). Found $head_version."; exit 1; } diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 33b9343..ac17520 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,9 +2,9 @@ name: Unit-Test-CI on: push: - branches: [ master, staging, development ] + branches: [ master, development ] pull_request: - branches: [ master, staging, development ] + branches: [ master, development ] jobs: build-test: diff --git a/.github/workflows/npm-publish.yml b/.github/workflows/npm-publish.yml index eab0907..d92c746 100644 --- a/.github/workflows/npm-publish.yml +++ b/.github/workflows/npm-publish.yml @@ -1,10 +1,11 @@ -# This workflow will publish a package to GitHub Packages when a release is created +# This workflow publishes packages when a release tag is pushed # For more information see: https://help.github.com/actions/language-and-framework-guides/publishing-nodejs-packages name: Publish package to NPM repository on: - release: - types: [created] + push: + tags: + - 'v*' jobs: publish-npm: diff --git a/AGENTS.md b/AGENTS.md index 2b82ff8..c50707c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -29,7 +29,7 @@ npm run download-regions # regions.json only (also run as part of prebuild) `npm test` runs **`pretest` → `npm run build`**, then Jest with coverage; outputs under **`reports/`**. Use **`npm run test:debug`** for Jest watch mode (`--runInBand`). -**CI:** `.github/workflows/ci.yml` (unit tests / coverage on `development`, `staging`, `master`). Branch rules: `.github/workflows/check-branch.yml`. Publish: `.github/workflows/npm-publish.yml` (on GitHub release). +**CI:** `.github/workflows/ci.yml` (unit tests / coverage on `development`, `master`). Publish: `.github/workflows/npm-publish.yml` (on tag push `v*`). Back-merge automation: `.github/workflows/back-merge-pr.yml`. Install: `npm i @contentstack/utils` — see root **`README.md`** and **`package.json`** for the current version. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 477c426..3364d13 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -6,7 +6,7 @@ Thank you for your interest in contributing to Contentstack Utils JavaScript. Th **All pull requests must be raised against the `development` branch.** -Do not open PRs against `master` or `staging`. Create your feature or fix branch from `development`, and open your PR to merge into `development`. Maintainers will handle promotion to other branches after review. +Feature/fix PRs should merge into `development`. Release PRs are raised by maintainers directly from `development` to `master`. ## Getting Started @@ -92,7 +92,7 @@ Husky is used for Git hooks. Before each commit, the pre-commit hook runs. Ensur git rebase upstream/development ``` -2. **Open a Pull Request** against the **`development`** branch (not `master` or `staging`). +2. **Open a Pull Request** against the **`development`** branch for feature/fix work. Maintainers handle release PRs from `development` to `master`. 3. **Fill out the PR template** (if one exists) and provide: - A clear title and description of the change diff --git a/skills/dev-workflow/SKILL.md b/skills/dev-workflow/SKILL.md index 184230b..7ad13fa 100644 --- a/skills/dev-workflow/SKILL.md +++ b/skills/dev-workflow/SKILL.md @@ -15,8 +15,8 @@ description: Branches, CI, build/test/lint commands, git hooks, PR expectations, ### Branches and PRs -- **CI** (`Unit-Test-CI`) runs on **push/PR** to `development`, `staging`, and `master` (`.github/workflows/ci.yml`). -- **Branch check:** PRs **into `staging`** from a head branch other than **`development`** fail (`.github/workflows/check-branch.yml`). Prefer **`development`** as the integration branch when aligning with upstream. +- **CI** (`Unit-Test-CI`) runs on **push/PR** to `development` and `master` (`.github/workflows/ci.yml`). +- Feature/fix PRs should target **`development`**. Release PRs are raised directly from **`development`** to **`master`**. - Confirm target branch with maintainers if unsure. ### Commands @@ -43,4 +43,4 @@ description: Branches, CI, build/test/lint commands, git hooks, PR expectations, - Version in **`package.json`** and **`CHANGELOG.md`**. - **`prepublishOnly`** runs **`npm test`**. -- **npm / GitHub Packages:** `.github/workflows/npm-publish.yml` on **release `created`**; secrets `NPM_TOKEN`, `GIT_TOKEN` (maintainers). +- **npm / GitHub Packages:** `.github/workflows/npm-publish.yml` on **tag push (`v*`)**; secrets `NPM_TOKEN`, `GIT_TOKEN` (maintainers). From ac486268fa536674aa19c998206b1e7b1b727bdd Mon Sep 17 00:00:00 2001 From: raj pandey Date: Wed, 29 Apr 2026 12:11:43 +0530 Subject: [PATCH 2/4] Fixed: the release createion based on the tag --- .github/workflows/npm-publish.yml | 13 +++++++++---- AGENTS.md | 2 +- skills/dev-workflow/SKILL.md | 2 +- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/.github/workflows/npm-publish.yml b/.github/workflows/npm-publish.yml index d92c746..3f8b11c 100644 --- a/.github/workflows/npm-publish.yml +++ b/.github/workflows/npm-publish.yml @@ -1,17 +1,19 @@ -# This workflow publishes packages when a release tag is pushed +# This workflow publishes packages when a GitHub Release is created for a version tag. # For more information see: https://help.github.com/actions/language-and-framework-guides/publishing-nodejs-packages name: Publish package to NPM repository on: - push: - tags: - - 'v*' + release: + types: [created] jobs: publish-npm: + if: ${{ startsWith(github.event.release.tag_name, 'v') && !github.event.release.draft }} runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + with: + ref: ${{ github.event.release.tag_name }} - uses: actions/setup-node@v4 with: node-version: '22.x' @@ -21,9 +23,12 @@ jobs: env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} publish-git: + if: ${{ startsWith(github.event.release.tag_name, 'v') && !github.event.release.draft }} runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + with: + ref: ${{ github.event.release.tag_name }} - uses: actions/setup-node@v4 with: node-version: '22.x' diff --git a/AGENTS.md b/AGENTS.md index c50707c..ee75585 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -29,7 +29,7 @@ npm run download-regions # regions.json only (also run as part of prebuild) `npm test` runs **`pretest` → `npm run build`**, then Jest with coverage; outputs under **`reports/`**. Use **`npm run test:debug`** for Jest watch mode (`--runInBand`). -**CI:** `.github/workflows/ci.yml` (unit tests / coverage on `development`, `master`). Publish: `.github/workflows/npm-publish.yml` (on tag push `v*`). Back-merge automation: `.github/workflows/back-merge-pr.yml`. +**CI:** `.github/workflows/ci.yml` (unit tests / coverage on `development`, `master`). Publish: `.github/workflows/npm-publish.yml` (GitHub **Release** created for tag `v*`; draft releases are skipped). Back-merge automation: `.github/workflows/back-merge-pr.yml`. Install: `npm i @contentstack/utils` — see root **`README.md`** and **`package.json`** for the current version. diff --git a/skills/dev-workflow/SKILL.md b/skills/dev-workflow/SKILL.md index 7ad13fa..93923d0 100644 --- a/skills/dev-workflow/SKILL.md +++ b/skills/dev-workflow/SKILL.md @@ -43,4 +43,4 @@ description: Branches, CI, build/test/lint commands, git hooks, PR expectations, - Version in **`package.json`** and **`CHANGELOG.md`**. - **`prepublishOnly`** runs **`npm test`**. -- **npm / GitHub Packages:** `.github/workflows/npm-publish.yml` on **tag push (`v*`)**; secrets `NPM_TOKEN`, `GIT_TOKEN` (maintainers). +- **npm / GitHub Packages:** `.github/workflows/npm-publish.yml` on **`release: types: [created]`** for tag **`v*`** (draft releases skipped); secrets `NPM_TOKEN`, `GIT_TOKEN` (maintainers). From 2108ce0e7d5ceead7aefe3c80437fd1e6180d947 Mon Sep 17 00:00:00 2001 From: raj pandey Date: Thu, 30 Apr 2026 11:59:13 +0530 Subject: [PATCH 3/4] fix: check-version bump triggered for all the PR --- .github/workflows/check-version-bump.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/check-version-bump.yml b/.github/workflows/check-version-bump.yml index 620af85..5799ae8 100644 --- a/.github/workflows/check-version-bump.yml +++ b/.github/workflows/check-version-bump.yml @@ -2,7 +2,6 @@ name: Check Version Bump on: pull_request: - branches: [main, master] jobs: check-version-bump: From 8d733658c9bd20582d77a7d19413cd206bc4b438 Mon Sep 17 00:00:00 2001 From: raj pandey Date: Thu, 30 Apr 2026 15:10:37 +0530 Subject: [PATCH 4/4] ci: gate version-bump on src paths; validate semver vs latest tag only --- .github/workflows/check-version-bump.yml | 23 +++++++++-------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/.github/workflows/check-version-bump.yml b/.github/workflows/check-version-bump.yml index 5799ae8..51e052c 100644 --- a/.github/workflows/check-version-bump.yml +++ b/.github/workflows/check-version-bump.yml @@ -1,3 +1,5 @@ +# Runs only when production code under src/ changes. Version must be > latest v* tag (not vs base branch). + name: Check Version Bump on: @@ -26,26 +28,21 @@ jobs: exit 0 fi - is_ignored_change() { + is_production_source_change() { local f="$1" - [[ "$f" =~ ^docs/ ]] && return 0 - [[ "$f" =~ ^\.github/ ]] && return 0 - [[ "$f" =~ (^|/)tests?/ ]] && return 0 - [[ "$f" =~ (^|/)__tests__/ ]] && return 0 - [[ "$f" =~ \.md$ ]] && [[ ! "$f" =~ (^|/)CHANGELOG\.md$ ]] && return 0 - return 1 + [[ "$f" == src/* ]] } - has_release_impact=false + has_source_changes=false for file in "${CHANGED_FILES[@]}"; do - if ! is_ignored_change "$file"; then - has_release_impact=true + if is_production_source_change "$file"; then + has_source_changes=true break fi done - if [ "$has_release_impact" = false ]; then - echo "Skipping docs/test-only PR." + if [ "$has_source_changes" = false ]; then + echo "Skipping: no src/ production code changes." exit 0 fi @@ -68,7 +65,6 @@ jobs: [ -n "$CHANGELOG_HEAD" ] || { echo "::error::Could not find a top changelog heading like '## vX.Y.Z' in $CHANGELOG_FILE."; exit 1; } [ "$CHANGELOG_HEAD" = "$head_version" ] || { echo "::error::$CHANGELOG_FILE top version ($CHANGELOG_HEAD) does not match project version ($head_version)."; exit 1; } - base_version=$(git show "$BASE_SHA:$VERSION_FILE" | node -e "let d=''; process.stdin.on('data', c => d += c); process.stdin.on('end', () => console.log(JSON.parse(d).version));") latest_tag=$(git tag --list 'v*' --sort=-version:refname | sed -n '1p') latest_version="${latest_tag#v}" [ -n "$latest_version" ] || latest_version="0.0.0" @@ -77,5 +73,4 @@ jobs: python3 -c 'import sys;v=lambda s:[int(x) if x.isdigit() else 0 for x in (s.strip().lstrip("v").split("-",1)[0].split("+",1)[0].split(".")+["0","0","0"])[:3]];print("true" if v(sys.argv[1])>v(sys.argv[2]) else "false")' "$1" "$2" } - [ "$(version_gt "$head_version" "$base_version")" = "true" ] || { echo "Version must be greater than base version ($base_version). Found $head_version."; exit 1; } [ "$(version_gt "$head_version" "$latest_version")" = "true" ] || { echo "Version must be greater than latest tag version ($latest_version). Found $head_version."; exit 1; }