Skip to content

chore: refactor release flow to create release on every push#2764

Open
stalniy wants to merge 4 commits intomainfrom
chore/release-on-every-push
Open

chore: refactor release flow to create release on every push#2764
stalniy wants to merge 4 commits intomainfrom
chore/release-on-every-push

Conversation

@stalniy
Copy link
Contributor

@stalniy stalniy commented Feb 18, 2026

Why

We have release PR right now which collects changes and then we have 1 big release and deployment. This causes issues because:

  • context is lost — by the time we release, we’ve already forgotten why something was done or what trade-offs were made or what deployment strategy we should apply
  • risk accumulates — many unrelated changes ship together, making rollback and root-cause analysis harder.
  • feedback loop is too long — bugs and architectural mistakes are discovered late, when fixing them is more expensive.

What

Summary by CodeRabbit

  • Chores

    • Added workflow-level concurrency and a per-run "cancel-if-stale" option, broadened release triggers, and updated deploy inputs/flags to avoid stale deployments.
    • Migrated release tooling to ESM, simplified releaser packaging, removed legacy per-app release scripts, and added a new bump-and-changelog script to compute versions and generate changelogs.
  • Documentation

    • Rewrote release workflow docs to reflect automated, GitHub Actions–driven releases and standardized tagging.
  • Revert

    • Removed the pre-release PR creation workflow.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 18, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • ✅ Review completed - (🔄 Check again to review again)
📝 Walkthrough

Walkthrough

Replaces release-it with a Node ESM bumper, migrates releaser utilities to ESM, updates releaser package metadata, removes a shell changelog extractor, and adds a cancel-if-stale concurrency input and runtime guard across multiple GitHub Actions release/deploy workflows.

Changes

Cohort / File(s) Summary
Workflows — add cancel-if-stale & concurrency
.github/workflows/console-api-release.yml, .github/workflows/console-web-release.yml, .github/workflows/indexer-release.yml, .github/workflows/notifications-release.yml, .github/workflows/provider-proxy-release.yml, .github/workflows/stats-web-release.yml, .github/workflows/tx-signer-release.yml
Added workflow_dispatch input cancel-if-stale and a top-level concurrency block; forwarded input to setup job as cancel-if-stale: ${{ inputs.cancel-if-stale == 'true' }}.
Workflow triggers & concurrency tweaks
.github/workflows/all-release.yml
Broadened path triggers (from apps/*/package.json to apps/**/*, added packages/**/*, package-lock.json) and removed prior concurrency block.
Removed workflow
.github/workflows/create-pre-release-pr.yml
Deleted pre-release-PR workflow that created release branches/PRs and handled PR-based release gating.
Reusable deploy setup & guards
.github/workflows/reusable-deploy-setup.yml, .github/workflows/reusable-release-app.yml, .github/workflows/reusable-release-nextjs-app.yml
Added cancel-if-stale input, new actions: read permission, introduced concurrency guard step that compares image_tag versions and can abort when a newer version exists; deploy invocations now pass cancel-if-stale=true and image_tag as input flags.
Release creation refactor
.github/workflows/reusable-create-github-release.yml
Switched concurrency key to github.sha, expanded checkout to full history, added Node setup and cache/install steps, replaced PR gating with packages/releaser/recommended-bump.js to compute tag/changelog, and changed release creation to gh release create.
Releaser — ESM migration & new script
packages/releaser/find-local-package-dependencies.js, packages/releaser/recommended-bump.js, packages/releaser/.release-it.js (deleted), packages/releaser/package.json
Migrated releaser to ESM (type: "module"), exported findLocalPackageDependencies as ESM, added recommended-bump.js to compute releaseType/nextTag/changelog and output JSON, removed .release-it.js, updated package.json dependencies/metadata.
Local action & linting
.github/actions/local-dependencies/action.yml, packages/dev-config/.eslintrc.base.js
Local action Node invocation changed to ESM (--input-type=module) and import path updated; ESLint config extended to include packages/releaser/**/*.js and set ecmaVersion: 2022.
Removed helper script
script/extract-changelog.sh
Deleted shell helper that extracted changelog sections by tag.
Repo-level deps
package.json
Removed devDependencies release-it and @release-it/conventional-changelog.

Sequence Diagram(s)

sequenceDiagram
    participant GH as "GitHub Actions"
    participant Bumper as "recommended-bump.js"
    participant Deps as "find-local-package-dependencies"
    participant Conv as "conventional-recommended-bump"
    participant GHCLI as "gh CLI / Actions runner"
    participant Setup as "reusable-deploy-setup (concurrency guard)"

    GH->>Bumper: run recommended-bump (tag-prefix, repo-url, path)
    Bumper->>Deps: request local package dependencies (path)
    Deps-->>Bumper: return dependency package paths
    Bumper->>Conv: load commits & compute whatBump
    Conv-->>Bumper: return releaseType + metadata
    Bumper->>Bumper: compute nextVersion/nextTag and build changelog
    Bumper-->>GH: output JSON (releaseType, versions, changelog)
    GH->>GHCLI: gh release create with computed tag & changelog
    GHCLI-->>GH: release created
    GH->>Setup: invoke reusable-deploy-setup (image_tag, cancel-if-stale)
    Setup->>Setup: concurrency guard compares image_tag to recent runs
    Setup-->>GH: allow or abort deployment
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • baktun14
  • ygrishajev

Poem

"🐇 I hopped from CommonJS into ESM bright,
I parsed commits by day and versioned by night,
Guards watch the queue so stale builds may rest,
Changelogs baked neat — now ship what's best,
A carrot-coded release — hop, delight!"

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ❓ Inconclusive The PR description includes the required 'Why' section with detailed motivation, but the 'What' section is incomplete—it only contains the template comment without explaining the actual implementation changes. Complete the 'What' section by describing the specific changes made (e.g., removal of release-PR workflow, addition of per-commit release creation, workflow modifications, etc.).
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly describes the main change: refactoring the release flow to create releases on every push instead of batched releases.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch chore/release-on-every-push

Comment @coderabbitai help to get the list of available commands and usage tips.

@codecov
Copy link

codecov bot commented Feb 18, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 51.98%. Comparing base (7d2914f) to head (25a9bcd).
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #2764      +/-   ##
==========================================
+ Coverage   51.96%   51.98%   +0.01%     
==========================================
  Files        1041     1041              
  Lines       27397    27397              
  Branches     6332     6357      +25     
==========================================
+ Hits        14238    14242       +4     
+ Misses      12695    12682      -13     
- Partials      464      473       +9     
Flag Coverage Δ
api 76.69% <ø> (+0.02%) ⬆️
deploy-web 37.40% <ø> (+0.01%) ⬆️
log-collector 75.35% <ø> (ø)
notifications 85.56% <ø> (ø)
provider-console 81.48% <ø> (ø)
provider-proxy 82.41% <ø> (ø)
tx-signer 79.29% <ø> (ø)
see 4 files with indirect coverage changes
🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 6

🧹 Nitpick comments (1)
.github/workflows/all-release.yml (1)

6-8: Broader triggers may cause more frequent workflow runs with empty matrices.

The path filter now triggers on any file change in apps/**/* or packages/**/*, but the inline script (line 42) still filters to only apps with changed package.json files. This means the workflow will run more frequently but may produce empty matrices.

The if: fromJson(needs.setup.outputs.backends).include[0] guards at lines 59 and 74 should skip jobs gracefully when matrices are empty, so this is likely intentional to support the "release on every push" objective.

Consider adding a comment explaining this design choice to help future maintainers understand the intentional trigger broadening.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/all-release.yml around lines 6 - 8, The workflow broadens
path filters to "apps/**/*" and "packages/**/*" but the setup script (the inline
script that filters to apps with changed package.json) can produce empty
matrices; update .github/workflows/all-release.yml by adding a concise comment
near the top trigger block and next to the if guards (the if:
fromJson(needs.setup.outputs.backends).include[0] checks) explaining this is
intentional: triggers are broadened to run on any repo change to enable "release
on every push", the setup script narrows to actual changed package.json files,
and the if guards are present to skip jobs when matrices are empty—this comment
will help future maintainers understand why empty matrices and extra workflow
runs are expected.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.github/workflows/reusable-create-github-release.yml:
- Around line 33-36: The sparse-checkout only includes ".github" so the workflow
cannot run the releaser script; update the workflow's "sparse-checkout" entries
to also include the releaser package and any package/app package.json files
required by packages/releaser (so that node
./packages/releaser/recommended-bump.js and
packages/releaser/find-local-package-dependencies.js plus the package.json files
under packages/ and apps/ are present). In short, add paths such as
"packages/releaser/**", "packages/**/package.json" and "apps/**/package.json"
(or simply "packages/**" and "apps/**") to the sparse-checkout block so the
releaser script and its dependency resolution can be executed.
- Around line 85-89: The current gh release create invocation interpolates the
changelog into the --notes argument which can break on quotes or other shell
metacharacters; change the step that runs gh release create to write
steps.bumps.outputs.changelog to a temporary file (or to an environment variable
and feed it via a heredoc) and pass that filepath to gh release create using
--notes-file instead of --notes, ensuring you still use the same tag from
steps.bumps.outputs.git_tag and repo from github.repository.

In `@packages/releaser/recommended-bump.js`:
- Around line 129-131: When result.releaseType is falsy the script currently
calls process.exit(1) with no diagnostics; before exiting, log a clear error
including the reason and relevant data (e.g., console.error("No releaseType
returned from recommended bump:", JSON.stringify(result)) or similar) so callers
can see why it failed, then call process.exit(1); update the block that checks
result.releaseType to emit that diagnostic using the result object (and any
contextual variables you have) before exiting.
- Around line 137-148: The switch on result.releaseType that assigns nextVersion
(variable nextVersion, switch on result.releaseType) lacks a default branch so
nextVersion can be left undefined; add a default case that either sets
nextVersion to the current version string (e.g., `${major}.${minor}.${patch}`)
or explicitly throws a clear error indicating an unsupported releaseType,
ensuring subsequent tag/changelog generation never receives undefined.
- Line 152: The changelog call is only passing the first commit by using
[result.commits[0]]; update the call to pass the full commits array
(result.commits) to generateChangelog so all commits since lastTag are included
(e.g., replace [result.commits[0]] with result.commits when calling
generateChangelog with { repoUrl, lastTag, nextTag, nextVersion }).
- Around line 113-127: The code calls a non-public Bumper method
getLastSemverTag(), which will fail at runtime; either remove the unused lastTag
assignment (delete the const lastTag = await bumper.getLastSemverTag(); line) or
if you need the last semver tag, replace that call by instantiating
ConventionalGitClient and calling its getLastSemverTag({ prefix:
values["tag-prefix"] }) (import ConventionalGitClient from
'@conventional-changelog/git-client', create new
ConventionalGitClient(process.cwd()), then await git.getLastSemverTag with the
prefix) while keeping existing bumper usage (loadPreset, tag, commits, bump).

---

Nitpick comments:
In @.github/workflows/all-release.yml:
- Around line 6-8: The workflow broadens path filters to "apps/**/*" and
"packages/**/*" but the setup script (the inline script that filters to apps
with changed package.json) can produce empty matrices; update
.github/workflows/all-release.yml by adding a concise comment near the top
trigger block and next to the if guards (the if:
fromJson(needs.setup.outputs.backends).include[0] checks) explaining this is
intentional: triggers are broadened to run on any repo change to enable "release
on every push", the setup script narrows to actual changed package.json files,
and the if guards are present to skip jobs when matrices are empty—this comment
will help future maintainers understand why empty matrices and extra workflow
runs are expected.

@stalniy stalniy force-pushed the chore/release-on-every-push branch from ea84c46 to b06b91e Compare February 18, 2026 11:42
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
.github/workflows/reusable-deploy-setup.yml (1)

56-62: ⚠️ Potential issue | 🟠 Major

Don’t strip -beta from the deployment image tag.

Line 60 removes -beta before output. If you dispatch with console-api/1.2.3-beta, this will deploy 1.2.3 instead of the beta image.

✅ Keep beta tags for deployment, normalize only for comparison
-          # Remove 'v' prefix if present
-          version="${version#v}"
-
-          # Remove beta suffix if present
-          version="${version%-beta}"
-
-          echo "image_tag=$version" >> "$GITHUB_OUTPUT"
+          # Remove 'v' prefix if present for image tags
+          version="${version#v}"
+          echo "image_tag=$version" >> "$GITHUB_OUTPUT"
-            const version = '${{ steps.extract.outputs.image_tag }}';
+            const version = '${{ steps.extract.outputs.image_tag }}'.replace(/-beta$/, '');
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/reusable-deploy-setup.yml around lines 56 - 62, The
workflow currently strips the "-beta" suffix via the statement
version="${version%-beta}" which causes beta dispatches to deploy the non-beta
image; instead keep the original version string (the variable named version)
when writing image_tag to GITHUB_OUTPUT so beta tags are preserved, and if you
need a normalized value for comparisons create a separate temp variable (e.g.,
normalized_version) that strips the "v" prefix and the "-beta" suffix for
comparison logic, leaving the image_tag echo as echo "image_tag=$version" >>
"$GITHUB_OUTPUT".
.github/workflows/all-release.yml (1)

6-44: ⚠️ Potential issue | 🟠 Major

Release matrix ignores most of the newly-triggered changes.

Line 6-8 now triggers on any apps/** or packages/** change, but Line 42 still only selects apps when apps/*/package.json changes. This makes runs no-op for normal app code changes and for package-only changes, which undermines “release on every push.”

✅ Minimal fix to capture app changes
-                .filter(f => f.match(/^apps\/[^/]+\/package\.json$/))
+                .filter(f => f.match(/^apps\/[^/]+\//))

Also decide how packages/** changes should map to releases (e.g., release all apps or compute dependents). If that dependency-mapping is broader than this PR, consider tracking it separately to keep scope tight. Based on learnings: In the Akash Network Console project, when cross-cutting concerns or broader refactoring issues are identified during PR review, the preferred approach is to create a separate GitHub issue to track the work rather than expanding the scope of the current PR.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/all-release.yml around lines 6 - 44, The matrix currently
only detects app changes when apps/*/package.json is modified, so change the
file filtering logic in the node script: replace the filter .filter(f =>
f.match(/^apps\/[^/]+\/package\.json$/)) with a pattern that captures any file
under an app directory (e.g., .filter(f => f.match(/^apps\/([^/]+)\//)) and
extract app name via .map(f => f.split("/")[1]) to capture normal app code
changes; additionally detect any changes matching ^packages\/ to trigger
releases for all apps (or set a flag to compute dependents later) by reading the
apps directory when packages are changed—update references to APP_ALIASES,
workflowFile, and the apps variable accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.github/workflows/reusable-deploy-setup.yml:
- Around line 64-113: Concurrency guard is broken because listWorkflowRuns
doesn't expose run.inputs, so run.inputs?.image_tag is always undefined; surface
the image_tag in a retrievable field (e.g., run.name or an artifact/output) and
parse that instead. Update callers of the reusable workflow to set a predictable
run name (or create a tiny artifact/output) that includes app and image_tag,
then change the script in the concurrency guard (the code using
github.rest.actions.listWorkflowRuns and checking run.inputs?.image_tag) to
extract image_tag from run.name (or fetch the artifact/output for that run) and
use that value for the existing parsing and isNewer comparison for version
blocking.

In @.github/workflows/reusable-release-app.yml:
- Around line 55-58: The gh workflow run invocation passes a malformed -f
argument (`-f ${{ needs.release.outputs.git_tag }}`); change it to a key=value
pair (e.g., `-f image_tag=${{ needs.release.outputs.git_tag }}` or `-f
git_tag=${{ needs.release.outputs.git_tag }}`) so the command accepts the input,
keeping the other flags (`--repo ${{ github.repository }}` and `-f
cancel-if-stale=true`) unchanged; update the gh workflow run line that
references inputs.deploy_workflow and needs.release.outputs.git_tag accordingly.

---

Outside diff comments:
In @.github/workflows/all-release.yml:
- Around line 6-44: The matrix currently only detects app changes when
apps/*/package.json is modified, so change the file filtering logic in the node
script: replace the filter .filter(f => f.match(/^apps\/[^/]+\/package\.json$/))
with a pattern that captures any file under an app directory (e.g., .filter(f =>
f.match(/^apps\/([^/]+)\//)) and extract app name via .map(f => f.split("/")[1])
to capture normal app code changes; additionally detect any changes matching
^packages\/ to trigger releases for all apps (or set a flag to compute
dependents later) by reading the apps directory when packages are changed—update
references to APP_ALIASES, workflowFile, and the apps variable accordingly.

In @.github/workflows/reusable-deploy-setup.yml:
- Around line 56-62: The workflow currently strips the "-beta" suffix via the
statement version="${version%-beta}" which causes beta dispatches to deploy the
non-beta image; instead keep the original version string (the variable named
version) when writing image_tag to GITHUB_OUTPUT so beta tags are preserved, and
if you need a normalized value for comparisons create a separate temp variable
(e.g., normalized_version) that strips the "v" prefix and the "-beta" suffix for
comparison logic, leaving the image_tag echo as echo "image_tag=$version" >>
"$GITHUB_OUTPUT".

---

Duplicate comments:
In @.github/workflows/reusable-create-github-release.yml:
- Around line 83-87: The release creation step interpolates
steps.bumps.outputs.changelog directly into a shell command which can break on
quotes or other metacharacters; change the step to write "${{
steps.bumps.outputs.changelog }}" to a temporary file (or export it as an env
var) and invoke gh release create using the --notes-file option (or pass the env
var safely quoted) instead of --notes, referencing the same outputs
(steps.bumps.outputs.changelog and steps.bumps.outputs.git_tag) so the command
becomes: write changelog -> gh release create "${{ steps.bumps.outputs.git_tag
}}" --repo "${{ github.repository }}" --title "${{ steps.bumps.outputs.git_tag
}}" --notes-file <temp-file-or-path> (or use a safely quoted env var).

In `@packages/releaser/recommended-bump.js`:
- Around line 129-131: The script currently calls process.exit(1) when
result.releaseType is falsy without any diagnostic output; update the check
around result.releaseType to emit a clear error (e.g., console.error or
processLogger.error) that includes context and any relevant data (such as the
full result object or inputs used to compute it) before exiting, so replace the
silent exit in the conditional that references result.releaseType with a logged
message plus process.exit(1).
- Around line 126-127: Replace the call to the non-public method
bumper.getLastSemverTag() with a call to ConventionalGitClient.getLastSemverTag:
import and instantiate ConventionalGitClient (e.g., const git = new
ConventionalGitClient(process.cwd())), then call await git.getLastSemverTag({
prefix: values["tag-prefix"] }) to obtain lastTag; remove or stop using
bumper.getLastSemverTag() and continue using bumper.bump(whatBump) as before.
- Line 152: The changelog call is only passing the first commit
(generateChangelog([result.commits[0]], ...)) so the release notes miss all
other commits; change the call to pass the full commits array (e.g.,
generateChangelog(result.commits, { repoUrl, lastTag, nextTag, nextVersion }))
or a properly filtered/ordered array derived from result.commits so all commits
since lastTag are included when generating the changelog.
- Around line 137-148: The switch on result.releaseType can leave nextVersion
undefined for unexpected values; update the switch in the block that computes
nextVersion to include a default branch that handles unknown release types
(e.g., throw a clear Error or set nextVersion to a safe fallback and log),
referencing the same variables (result.releaseType, nextVersion, major, minor,
patch) so the function always produces a valid nextVersion or fails loudly.

@stalniy stalniy force-pushed the chore/release-on-every-push branch 2 times, most recently from ecb93d9 to 956284e Compare February 18, 2026 12:09
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In @.github/workflows/reusable-create-github-release.yml:
- Around line 83-87: The workflow currently passes the changelog via the gh
CLI's --notes which allows shell metacharacter injection; change the run step to
write "${{ steps.bumps.outputs.changelog }}" into a temporary file (e.g., with
mktemp or here-doc) and call gh release create with --notes-file pointing to
that temp file instead of --notes, then remove the temp file after creating the
release; update the invocation that uses --notes and the reference to "${{
steps.bumps.outputs.changelog }}" accordingly to use the temp file path.

In @.github/workflows/reusable-release-app.yml:
- Around line 57-58: The -f argument is malformed: change the flag usage from
"-f ${{ needs.release.outputs.git_tag }}" to include a key=value pair such as
"-f git_tag=${{ needs.release.outputs.git_tag }}" so the gh workflow run
receives an input name; keep the existing "-f cancel-if-stale=true" as-is.
Ensure the key ("git_tag") matches the workflow's expected input name.

@stalniy stalniy force-pushed the chore/release-on-every-push branch 5 times, most recently from 558fbb0 to 17162f1 Compare February 18, 2026 12:28
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
.github/workflows/all-release.yml (1)

6-43: ⚠️ Potential issue | 🟠 Major

Align the release matrix with the expanded trigger paths.

With paths now covering any app/package changes, the setup step still derives apps only from apps/*/package.json. That means most app code changes (or package updates) trigger the workflow but produce an empty matrix and no releases. Consider deriving the app list from any file under apps/<app>/ (and define how package changes should map to releases).

🔧 Suggested adjustment to app detection
-                .filter(f => f.match(/^apps\/[^/]+\/package\.json$/))
-                .map(f => f.split("/")[1])
+                .filter(f => f.match(/^apps\/[^/]+\//))
+                .map(f => f.split("/")[1])
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/all-release.yml around lines 6 - 43, The app detection
currently only picks up changed package.json files via the filter
/\.match(/^apps\/[^/]+\/package\.json$/)/, so extend it to detect any change
under an app directory and still extract the app name; replace that filter with
one matching any path under apps (e.g. .filter(f => f.match(/^apps\/[^/]+\//)))
and keep the .map(f => f.split("/")[1]) to derive the app name; update any
comments/variables that reference APP_ALIASES or workflowFile to ensure
package-level changes (and other file changes under apps/<app>/) are mapped to
the correct release workflow using workflowFile(app).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.github/workflows/console-api-release.yml:
- Line 26: The workflow currently compares the boolean input via a string
equality check for the input named cancel-if-stale; update the expression to use
the boolean input directly (e.g., use the cancel-if-stale input value itself or
compare explicitly to true) so the cancel-if-stale condition uses a direct
boolean reference instead of string comparison.

In @.github/workflows/reusable-deploy-setup.yml:
- Around line 64-113: The stale-run guard currently ignores runs where
inputs.image_tag is an unprefixed tag (e.g., "v1.2.3"), because the split into
otherPrefix/otherRawVersion expects "app/version"; in the loop that reads
otherImageTag (the block using otherImageTag.split('/', 2) and computing
otherPrefix/otherRawVersion and otherVersion), treat tags without a slash as
belonging to the current app: if otherImageTag.includes('/') use the existing
split, otherwise set otherPrefix = app and otherRawVersion = otherImageTag, then
continue normal normalization (otherRawVersion.replace(/^v/,
'').replace(/-beta$/, '')) and comparison via isNewer.

In `@packages/releaser/recommended-bump.js`:
- Around line 126-136: When result.releaseType is falsy (the no-release path)
replace the current console.error+process.exit(0) with emitting a minimal JSON
object to stdout so downstream jq parsing succeeds; update the branch that
checks result.releaseType in recommended-bump.js (around the
bumper.getLastSemverTag()/bumper.bump() logic) to console.log a stable JSON
payload (e.g., indicating releaseNeeded: false, releaseType: null and optionally
currentVersion from the computed currentVersion variable) and then exit(0) —
ensure you reference result.releaseType, currentVersion, and bumper.bump when
locating the block to modify.

---

Outside diff comments:
In @.github/workflows/all-release.yml:
- Around line 6-43: The app detection currently only picks up changed
package.json files via the filter /\.match(/^apps\/[^/]+\/package\.json$/)/, so
extend it to detect any change under an app directory and still extract the app
name; replace that filter with one matching any path under apps (e.g. .filter(f
=> f.match(/^apps\/[^/]+\//))) and keep the .map(f => f.split("/")[1]) to derive
the app name; update any comments/variables that reference APP_ALIASES or
workflowFile to ensure package-level changes (and other file changes under
apps/<app>/) are mapped to the correct release workflow using workflowFile(app).

---

Duplicate comments:
In @.github/workflows/provider-proxy-release.yml:
- Around line 10-26: The workflow compares the input string to 'true' which is
brittle; replace the string equality check in the jobs.setup 'with:
cancel-if-stale' assignment with a proper boolean parse using fromJson.
Specifically, change the value passed to the reusable workflow from ${{
inputs.cancel-if-stale == 'true' }} to ${{ fromJson(inputs.cancel-if-stale) }}
so the 'cancel-if-stale' input is reliably converted to a boolean.

In `@packages/releaser/recommended-bump.js`:
- Around line 151-163: The changelog is being generated from only the first
commit because generateChangelog is called with [result.commits[0]]; update the
call to pass the full commit array (result.commits) so that
generateChangelog(repoUrl, lastTag, nextTag, nextVersion) receives all commits
and the generatedChangelog/variable changelog includes every commit rather than
just the first one (refer to symbols: generateChangelog, result.commits,
changelog, nextTag, repoUrl).

@stalniy stalniy force-pushed the chore/release-on-every-push branch 2 times, most recently from 9bd7b3e to 82206da Compare February 18, 2026 12:43
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In @.github/workflows/reusable-deploy-setup.yml:
- Around line 104-107: Split otherImageTag into parts and normalize prefix vs
raw version so otherRawVersion is never undefined: use something like parts =
otherImageTag.split('/', 2); set otherPrefix = parts.length === 2 ? parts[0] :
undefined and otherRawVersion = parts.length === 2 ? parts[1] : parts[0]; then
use otherVersion = otherRawVersion.replace(/^v/, '').replace(/-beta$/, ''); this
ensures otherRawVersion (and the replace calls) are safe while preserving the
existing comparison on otherPrefix (otherPrefix !== app).

In `@packages/releaser/recommended-bump.js`:
- Line 153: The changelog call is only passing the first commit
([result.commits[0]]) so generateChangelog only receives a single commit; change
the call to pass the entire commits array (e.g., result.commits) to
generateChangelog and, if necessary, guard for an empty array before calling.
Update the expression assigning changelog (the generateChangelog invocation) to
use result.commits instead of [result.commits[0]] so all commits since lastTag
are included.
- Around line 126-132: When the call to bumper.bump(whatBump) returns no
result.releaseType, ensure you emit a minimal JSON payload to stdout (not just
stderr) so downstream steps can parse it; update the branch that currently does
console.error(JSON.stringify({ error: "No releasable commits found" })) to also
write a JSON object to stdout (for example via console.log) indicating the
no-release condition and then exit with code 0, referencing the same
bumper.bump() result handling around lastTag and result.releaseType.

@stalniy stalniy force-pushed the chore/release-on-every-push branch 3 times, most recently from 64d3583 to e4a8cfd Compare February 18, 2026 15:36
@stalniy stalniy force-pushed the chore/release-on-every-push branch from e4a8cfd to c3d3b0a Compare February 18, 2026 17:37
@stalniy stalniy force-pushed the chore/release-on-every-push branch 2 times, most recently from bbb2f67 to 0f777a3 Compare February 18, 2026 17:49
@stalniy stalniy force-pushed the chore/release-on-every-push branch from 0f777a3 to 9d8352d Compare February 18, 2026 18:01
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR refactors the release workflow to create releases automatically on every push to main, eliminating the pre-release PR workflow. The changes migrate the releaser tooling from CommonJS to ESM, replace release-it with a custom recommended-bump.js script, and enhance deployment workflows with concurrency controls to prevent stale deployments.

Changes:

  • Replaced the pre-release PR workflow with automatic releases triggered on every push to main affecting apps/**/*, packages/**/*, or package-lock.json
  • Migrated packages/releaser to ESM and replaced release-it with a custom versioning/changelog script based on conventional-recommended-bump
  • Added cancel-if-stale concurrency guards to deployment workflows to skip deployments when newer versions are queued
  • Removed .release-it.js config files and release npm scripts from all apps
  • Updated documentation to reflect the new continuous release workflow

Reviewed changes

Copilot reviewed 40 out of 41 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
packages/releaser/recommended-bump.js New script for determining semantic version bumps and generating changelogs based on conventional commits
packages/releaser/find-local-package-dependencies.js Migrated from CommonJS to ESM to support the new module system
packages/releaser/package.json Updated to use ESM ("type": "module") and replaced dependencies
.github/workflows/all-release.yml Modified to trigger on broader path patterns and detect changed apps for release
.github/workflows/reusable-create-github-release.yml Refactored to use the new recommended-bump.js script instead of parsing PR changes
.github/workflows/reusable-deploy-setup.yml Added concurrency guard logic to cancel stale deployments
.github/workflows/*-release.yml Added cancel-if-stale input and workflow-level concurrency controls
.github/workflows/create-pre-release-pr.yml Removed (no longer needed with automatic releases)
script/extract-changelog.sh Removed (changelog extraction now handled by JavaScript)
apps/*/package.json Removed release scripts from all app package.json files
apps/*/.release-it.js Removed config files for the old release-it tooling
package.json Removed release-it and related dependencies, updated esbuild version
doc/release-workflow.md Comprehensive documentation update explaining the new workflow
Comments suppressed due to low confidence (1)

.github/actions/local-dependencies/action.yml:25

  • The inline Node.js script uses dynamic import which requires the file to be treated as an ES module. However, this might fail if Node.js doesn't have access to the file system in the expected way when using --input-type=module with inline code.

Consider changing this to execute the script as a file instead:

node -e "
  import('./packages/releaser/find-local-package-dependencies.js').then(({ findLocalPackageDependencies }) => {
    const localDependencies = findLocalPackageDependencies('./${{ inputs.path }}')
      .map(path => \`\${path}/**\`);
    console.log(JSON.stringify(localDependencies));
  });
"

Or create a dedicated CLI script file in the releaser package to avoid inline module imports.

        node --input-type=module -e '
          import { findLocalPackageDependencies } from "./packages/releaser/find-local-package-dependencies.js";
          const localDependencies = findLocalPackageDependencies("./${{ inputs.path }}")
            .map(path => `${path}/**`);
          console.log(JSON.stringify(localDependencies));
        ' | xargs -I "{}" -0 echo "deps_json={}" >> $GITHUB_OUTPUT

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +51 to +52
reason:
breakings === 1 ? `There is ${breakings} BREAKING CHANGE and ${features} features` : `There are ${breakings} BREAKING CHANGES and ${features} features`
Copy link

Copilot AI Feb 18, 2026

Choose a reason for hiding this comment

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

The reason message in the whatBump function has inconsistent grammar. When breakings === 0, it will output "There are 0 BREAKING CHANGES and X features" which sounds awkward.

Consider improving the logic:

reason: breakings === 0 
  ? `There are ${features} feature${features === 1 ? '' : 's'}` 
  : breakings === 1 
    ? `There is ${breakings} BREAKING CHANGE and ${features} feature${features === 1 ? '' : 's'}` 
    : `There are ${breakings} BREAKING CHANGES and ${features} feature${features === 1 ? '' : 's'}`

This provides better messages for all cases and handles singular/plural forms correctly.

Copilot uses AI. Check for mistakes.
@@ -4,13 +4,9 @@
"description": "Package containing console specific docker files and scripts",
Copy link

Copilot AI Feb 18, 2026

Choose a reason for hiding this comment

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

The package description is outdated. It says "Package containing console specific docker files and scripts" but this package is now the releaser package that handles versioning and changelog generation, not Docker-related functionality.

Update the description to something like: "Package for automated versioning and changelog generation based on Conventional Commits"

Copilot uses AI. Check for mistakes.
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
- "apps/**/*"
- "packages/**/*"
Copy link

Copilot AI Feb 18, 2026

Choose a reason for hiding this comment

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

The path filter now triggers on any change under packages/**/*, which means changes to non-releasable packages (like packages/dev-config, packages/docker) will trigger the release workflow unnecessarily.

Consider being more specific about which package changes should trigger releases. For example:

  • Only trigger on changes to packages that are actual dependencies of apps
  • Exclude infrastructure/tooling packages that don't affect runtime behavior
  • Or explicitly filter by checking if the changed files affect app dependencies

Currently, a change to packages/dev-config/.eslintrc.base.js will trigger all app release checks, even though it doesn't affect runtime behavior.

Suggested change
- "packages/**/*"
- "packages/**/*"
- "!packages/dev-config/**"
- "!packages/docker/**"

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

they are actually important but I need to remap them into apps. Find all dependent apps

if (otherApp !== app || !otherRawVersion) continue;

const otherVersion = otherRawVersion?.replace(/^v/, '').replace(/-beta$/, '');
if (otherVersion && otherVersion === version) continue;
Copy link

Copilot AI Feb 18, 2026

Choose a reason for hiding this comment

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

The isNewer function compares version numbers but returns false when versions are equal, yet line 110 explicitly skips when otherVersion === version. This makes line 110 redundant since isNewer(otherVersion, version) would already return false for equal versions.

Consider simplifying by removing the redundant check on line 110, or keeping it for clarity but adding a comment explaining it's for early exit optimization.

Suggested change
if (otherVersion && otherVersion === version) continue;

Copilot uses AI. Check for mistakes.
Comment on lines +70 to +77
next_tag=$(echo "$commits_analysis" | jq -r '.nextTag')
changelog=$(echo "$commits_analysis" | jq -r '.changelog')
echo "git_tag=$next_tag" >> "$GITHUB_OUTPUT"
{
echo "changelog<<CHANGELOG_EOF"
echo "$changelog"
echo "CHANGELOG_EOF"
} >> "$GITHUB_OUTPUT"
Copy link

Copilot AI Feb 18, 2026

Choose a reason for hiding this comment

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

The recommended-bump.js script outputs JSON to stdout and may also output to stderr in case of errors (line 130: console.error). When the script encounters no releasable commits, it outputs an error JSON object and exits with code 0.

The workflow should check if the next_tag is null or empty after parsing, otherwise it will attempt to create a release with an empty/null tag when there are no releasable commits. Consider adding error handling:

if [ "$next_tag" = "null" ] || [ -z "$next_tag" ]; then
  echo "No releasable commits found for $APP_NAME"
  exit 0
fi

This check should be added after line 70 to exit early when there's nothing to release.

Copilot uses AI. Check for mistakes.
repo: context.repo.repo,
workflow_id: currentRun.workflow_id,
status,
per_page: 10
Copy link

Copilot AI Feb 18, 2026

Choose a reason for hiding this comment

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

The concurrency guard script only checks the last 10 workflow runs per status (per_page: 10). In a high-velocity repository with many deployments, this might miss a newer version that's outside this window.

Consider either:

  1. Increasing per_page to a higher value (e.g., 50 or 100) to check more runs
  2. Adding pagination to check all recent runs
  3. Filtering by created_at date to check all runs from the last N hours/days

The current implementation could allow stale deployments to proceed if there are many intervening workflow runs.

Suggested change
per_page: 10
per_page: 100

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

unlikely to have more than 10 concurrent runs per app


const nextTag = `${prefix}${nextVersion}`;
const repoUrl = values["repo-url"] || "";
const changelog = generateChangelog([result.commits[0]], { repoUrl, lastTag, nextTag, nextVersion });
Copy link

Copilot AI Feb 18, 2026

Choose a reason for hiding this comment

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

The generateChangelog function is being called with only the first commit (result.commits[0]), but it should be called with all commits (result.commits). This will cause the changelog to only include one commit instead of all releasable commits since the last tag.

Change this to:

const changelog = generateChangelog(result.commits, { repoUrl, lastTag, nextTag, nextVersion });

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

this is intentional, because release creation is done in parallel and previous tags may not be pushed yet, so commit analyzer will receive more than 1 commit but changelog should generate only for a single commit because other instances of release workflow will create tags and releases for other commits

RELEASE_CHANGELOG: ${{ steps.bumps.outputs.changelog }}
RELEASE_VERSION: ${{ steps.bumps.outputs.git_tag }}
run: |
gh release create "$RELEASE_VERSION" \
Copy link
Contributor Author

Choose a reason for hiding this comment

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

issue(blocking): handle race condition when release with such tag exists. Need to retry and recompute the version or maybe just add a suffix '${version}-${timestamp}'

Comment on lines 15 to 17
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}-${{ inputs.app }}
group: ${{ github.workflow }}-${{ github.sha }}-${{ inputs.app }}
cancel-in-progress: true
Copy link
Contributor Author

Choose a reason for hiding this comment

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

suggestion(blocking): remove concurrency group because it doesn't make sense to have group per sha, because sha are unique

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.github/workflows/reusable-create-github-release.yml:
- Around line 40-51: The cache only stores packages/releaser/node_modules so
hoisted workspace deps in the root node_modules are missed on cache-hit; update
the actions/cache step (id: deps-cache) to include both node_modules and
packages/releaser/node_modules (e.g. path: | node_modules
packages/releaser/node_modules) or otherwise cache the workspace root
node_modules so hoisted dependencies like conventional-recommended-bump are
restored, and keep the existing key (deps-${{ runner.os }}-${{
hashFiles('package-lock.json') }}) or broaden hashFiles to include workspace
lock files if needed; leave the Install Dependencies step (if:
steps.deps-cache.outputs.cache-hit != 'true' / run: npm run safe-install -- -w
packages/releaser) unchanged.

---

Duplicate comments:
In @.github/workflows/console-api-release.yml:
- Line 26: The expression currently compares the workflow_dispatch boolean input
"cancel-if-stale" to the string 'true', which can mis-evaluate; update the
conditional where "cancel-if-stale" is used (the cancel-if-stale input
expression) to use the boolean directly (e.g., use "${{ inputs.cancel-if-stale
}}" or compare to true) so the workflow uses the actual boolean value rather
than a string comparison.

In `@packages/releaser/recommended-bump.js`:
- Line 153: The changelog call is only passing the first commit
(generateChangelog([result.commits[0]], ...)) so it omits all other commits;
change the argument to pass the full commits array (result.commits) to
generateChangelog (or a filtered/ sliced subset if intentional) so the generated
changelog includes all commits since the last release.
- Around line 129-132: The no-release branch in the recommended-bump.js check
for result.releaseType currently writes the JSON error to stderr via
console.error, which breaks downstream jq processing; update the block that
references result.releaseType so it emits a minimal JSON payload to stdout
(e.g., use console.log or process.stdout.write) instead of console.error while
keeping the same exit behavior (process.exit(0)) so pipelines that read stdout
can parse the output consistently.

Comment on lines +40 to +51
- name: Restore Dependencies Cache
uses: actions/cache@v4
id: deps-cache
with:
gh-user-to-slack-user: ${{ vars.GH_USER_TO_SLACK_USER }}
path: |
packages/releaser/node_modules
key: deps-${{ runner.os }}-${{ hashFiles('package-lock.json') }}

- name: Install Dependencies
if: steps.deps-cache.outputs.cache-hit != 'true'
run: |
npm run safe-install -- -w packages/releaser
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Inspect workspace configuration and install strategy to confirm where npm places deps.
echo "=== Root package.json ===" && cat -n package.json && \
echo -e "\n=== .npmrc files ===" && fd -H '^\.npmrc$' -x cat -n {} || echo "No .npmrc found" && \
echo -e "\n=== packages/releaser/package.json ===" && cat -n packages/releaser/package.json && \
echo -e "\n=== safe-install script ===" && rg -A 5 '"safe-install"' package.json

Repository: akash-network/console

Length of output: 4836


🏁 Script executed:

#!/bin/bash
# Inspect the safe-install script behavior
cat -n script/safe-deps-install.sh

Repository: akash-network/console

Length of output: 2840


Cache path misses hoisted workspace dependencies on cache-hit.

npm v11.6.2 with default workspace hoisting places transitive dependencies in the root node_modules directory, not in packages/releaser/node_modules. When the cache hits and install is skipped, the root node_modules (containing hoisted dependencies for conventional-recommended-bump) is not restored, breaking the releaser workflow.

🛠️ Proposed change
         with:
           path: |
+            node_modules
             packages/releaser/node_modules
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- name: Restore Dependencies Cache
uses: actions/cache@v4
id: deps-cache
with:
gh-user-to-slack-user: ${{ vars.GH_USER_TO_SLACK_USER }}
path: |
packages/releaser/node_modules
key: deps-${{ runner.os }}-${{ hashFiles('package-lock.json') }}
- name: Install Dependencies
if: steps.deps-cache.outputs.cache-hit != 'true'
run: |
npm run safe-install -- -w packages/releaser
- name: Restore Dependencies Cache
uses: actions/cache@v4
id: deps-cache
with:
path: |
node_modules
packages/releaser/node_modules
key: deps-${{ runner.os }}-${{ hashFiles('package-lock.json') }}
- name: Install Dependencies
if: steps.deps-cache.outputs.cache-hit != 'true'
run: |
npm run safe-install -- -w packages/releaser
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/reusable-create-github-release.yml around lines 40 - 51,
The cache only stores packages/releaser/node_modules so hoisted workspace deps
in the root node_modules are missed on cache-hit; update the actions/cache step
(id: deps-cache) to include both node_modules and packages/releaser/node_modules
(e.g. path: | node_modules packages/releaser/node_modules) or otherwise cache
the workspace root node_modules so hoisted dependencies like
conventional-recommended-bump are restored, and keep the existing key (deps-${{
runner.os }}-${{ hashFiles('package-lock.json') }}) or broaden hashFiles to
include workspace lock files if needed; leave the Install Dependencies step (if:
steps.deps-cache.outputs.cache-hit != 'true' / run: npm run safe-install -- -w
packages/releaser) unchanged.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

Comments