Skip to content

Conversation

@luxass
Copy link
Member

@luxass luxass commented Dec 4, 2025

This PR rewrites the entire release scripts library in effect. There is probably a lot of things, that could be done way better, since i haven't written any effect before.

Summary by CodeRabbit

  • New Features

    • Unified release scripts API with service-based runtime; workspace package listing and top-level prepare/verify/publish entrypoints.
    • New services for Git, GitHub, workspace discovery, version calculation, dependency ordering, and package updates enabling automated release flows.
  • Changes

    • Major refactor to effect-driven architecture and updated core dependencies and TypeScript tooling.
  • Documentation

    • Removed legacy HOW_IT_WORKS release workflow document.
  • Chores

    • Updated package manager config and TypeScript language-service plugin.
  • Tests

    • Added helper tests; removed several legacy release-related test suites.

✏️ Tip: You can customize this high-level summary in your review settings.

This commit deletes the `_shared.ts` file and several test files that were no longer needed, including `changelog.authors.test.ts`, `changelog.test.ts`, `git.test.ts`, and `commits.test.ts`. The removed tests were primarily focused on creating mock data and testing functionalities that are either deprecated or have been refactored in other parts of the codebase. This cleanup helps streamline the test suite and reduces maintenance overhead.
@coderabbitai
Copy link

coderabbitai bot commented Dec 4, 2025

Warning

Rate limit exceeded

@luxass has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 2 minutes and 32 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between 9a18e18 and 87d3186.

📒 Files selected for processing (5)
  • src/services/dependency-graph.service.ts (1 hunks)
  • src/services/git.service.ts (1 hunks)
  • src/services/github.service.ts (1 hunks)
  • src/services/version-calculator.service.ts (1 hunks)
  • src/utils/helpers.ts (1 hunks)

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

Replaces procedural release tooling with an Effect-based service architecture: many core modules and legacy versioning files removed; new services added for Git, GitHub, Workspace, Version calculation, Dependency graph, and Package updates; options normalization, typed errors, helper utilities, tests, and a consolidated createReleaseScripts entrypoint introduced.

Changes

Cohort / File(s) Change Summary
IDE & TS config
\.vscode/settings.json, tsconfig.json
Add VSCode TypeScript SDK path and enable @effect/language-service TS plugin.
Package manifest
package.json
Bump pnpm, add/update Effect ecosystem deps/devDeps, add #services/* import alias.
Removed docs
HOW_IT_WORKS.md
Remove long-form release workflow documentation.
Core procedural removals
src/core/*
src/core/git.ts, src/core/github.ts, src/core/changelog.ts, src/core/workspace.ts, src/core/prompts.ts
Delete procedural Git/GitHub/changelog/workspace/prompts modules and their public APIs.
Shared removals
src/shared/*
src/shared/options.ts, src/shared/utils.ts, src/shared/types.ts
Remove shared option normalization, utilities, flags, logger, and shared types.
Versioning removals
src/versioning/*
src/versioning/commits.ts, src/versioning/package.ts, src/versioning/version.ts
Remove legacy commit analysis, dependency graph, and versioning utilities.
New services
src/services/*
src/services/git.service.ts, src/services/github.service.ts, src/services/workspace.service.ts, src/services/dependency-graph.service.ts, src/services/package-updater.service.ts, src/services/version-calculator.service.ts
Add Effect-based services exposing Git, GitHub, workspace discovery, dependency topological ordering, version calculation, and package.json update APIs.
Helpers & utils added
src/utils/helpers.ts
Add overrides loading, commit schemas, package commit enrichment, global-commit detection, dependency-file checks, and commit-range discovery.
Errors
src/errors.ts
Add strongly-typed tagged error classes (GitError, GitCommandError, GitNotRepositoryError, BranchNotFoundError, WorkspaceError, GitHubError, VersionCalculationError, ExternalCommitParserError, OverridesLoadError).
Options & DI
src/options.ts
Add ReleaseScripts options types, normalization function, defaults, and ReleaseScriptsOptions Context.Tag for DI.
Main entrypoint
src/index.ts
Replace individual exports with createReleaseScripts factory and AppLayer wiring exposing verify, prepare, publish, and packages APIs.
Verify flow
src/verify.ts
Replace procedural verify with constructVerifyProgram built from Effect services.
Removed orchestration
src/release.ts, src/publish.ts
Remove old release/publish procedural functions; publish remains an unimplemented scaffold in new entrypoint.
Tests removed
test/_shared.ts, test/core/changelog*.test.ts, test/core/git.test.ts, test/versioning/commits.test.ts
Delete tests and helpers tied to removed modules.
New tests
test/helpers.test.ts, test/options.test.ts
Add tests for helper utilities and options normalization / DI.
Types & schemas added
src/services/*, src/utils/helpers.ts
Add new public types and schemas (WorkspacePackage, PackageRelease, PackageUpdateOrder, GitCommitSchema, WorkspacePackageWithCommits, etc.).

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant CLI as CLI (createReleaseScripts)
  participant App as Index / AppLayer
  participant Git as GitService
  participant WS as WorkspaceService
  participant GH as GitHubService
  participant Calc as VersionCalculatorService
  participant Graph as DependencyGraphService
  participant Upd as PackageUpdaterService

  CLI->>App: createReleaseScripts(options)
  App->>Git: assertWorkspaceReady()
  App->>WS: discoverWorkspacePackages()
  WS-->>App: packages[]
  App->>Git: get commits & file-changes
  Git-->>App: commits, file-changes
  App->>Calc: calculateBumps(packages, commits, overrides)
  Calc-->>App: releases[]
  App->>Graph: topologicalOrder(releases/packages)
  Graph-->>App: orderedPlan
  alt prepare + apply
    App->>Upd: applyReleases(orderedPlan)
    Upd->>Git: write package.json / stage / commit / push
    Upd->>GH: upsertPullRequest(...)
  else verify
    App->>GH: getPullRequestByBranch(branch)
    GH-->>App: PR or null
    App->>Git: checkout branch (if needed)
  end
  App-->>CLI: result / exit
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

  • Areas to focus review on:
    • Effect layer wiring, Context.Tag usage, and service dependency injection (src/index.ts, src/options.ts).
    • GitService command execution, dry-run semantics, and error mapping to GitCommandError.
    • GitHubService request/response decoding, schema validation, and GitHubError wrapping.
    • Commit merging, global-commit detection, and file-change mapping in src/utils/helpers.ts (edge cases, batching).
    • VersionCalculatorService and PackageUpdaterService bump logic, overrides handling, and package.json write semantics.
    • Removal impact: ensure callers of removed core/shared/versioning modules reference new services.

Possibly related PRs

Poem

🐰 I hopped through code with nimble paws,
Replaced old scripts and trimmed the claws.
Commits now dance in ordered rows,
Services hum where logic flows.
A carrot cheer — new layers, lighter flaws! 🥕

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 10.81% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'refactor: rewrite in effect' directly and accurately summarizes the primary change: a comprehensive rewrite of the release-scripts codebase to use the Effect library.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

- Removed the `HOW_IT_WORKS.md` documentation file.
- Introduced `ConfigService` to manage configuration options more effectively.
- Updated `createReleaseScripts` to utilize normalized options and improve clarity.
- Enhanced `GitService` and `GitHubService` to integrate with the new configuration structure.
- Added `options.ts` to define and normalize options for the release process.
- Created a new `changelog.ts` file for future changelog management.
- Introduced `GitHubError` for better error handling in GitHub API interactions.
- Refactored `GitService` to utilize `ConfigOptions` instead of `ConfigService`.
- Added dry run mode to options, allowing for non-destructive testing of commands.
- Enhanced `GitService` with new methods for branch management and command execution.
- Created tests for `GitService` to ensure proper functionality and error handling.
… features

- Added error handling for branch checkout in `playground.ts`.
- Introduced `ExternalCommitParserError` and `OverridesLoadError` for better error management in `errors.ts`.
- Updated `GitService` to include new methods for commit parsing and file reading.
- Refactored `ConfigOptions` to improve configuration handling in `options.ts`.
- Implemented new utility functions in `helpers.ts` for managing version overrides and merging commits.
- Enhanced `WorkspaceService` to support discovering workspace packages and reading package.json files.
…tion

- Added `DependencyGraphService` to manage package dependencies and their update order.
- Introduced `VersionCalculatorService` to calculate version bumps based on commit types.
- Created `PackageUpdaterService` to apply version updates to package.json files.
- Refactored imports in `index.ts` to utilize new service structure.
- Removed unused `changelog.ts` file.
- Updated `package.json` to reflect new service imports.
- Introduced `ReleaseScriptsOptions` for improved configuration handling.
- Refactored `createReleaseScripts` to utilize `ReleaseScriptsOptionsInput`.
- Updated `GitService`, `GitHubService`, and `PackageUpdaterService` to use the new options structure.
- Added `assertWorkspaceReady` method in `GitService` for workspace validation.
- Removed deprecated `ConfigOptions` and related tests.
- Added tests for `normalizeReleaseScriptsOptions` to ensure correct behavior.
…indCommitRange` functions

This commit introduces a comprehensive suite of unit tests for the functions `isGlobalCommit`, `isDependencyFile`, and `findCommitRange` in the `helpers` module. The tests cover various scenarios to ensure the correctness of these functions, including edge cases and expected behaviors with different input conditions.
@luxass luxass marked this pull request as ready for review December 7, 2025 15:37
Copilot AI review requested due to automatic review settings December 7, 2025 15:37
…m-lock.yaml`

This change cleans up the `pnpm-lock.yaml` file by removing the `@effect/cli` package and its associated dependencies, which are no longer needed. This helps to streamline the dependency graph and reduce potential conflicts in future updates.
This commit cleans up the code change entries by removing any empty `<code-changes>` tags. This helps maintain a tidy and organized structure in the change logs, ensuring that only relevant changes are documented.
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 represents a complete rewrite of the release scripts library using the Effect functional programming library. The author notes they haven't written Effect code before, which is evident in some areas where patterns could be improved. The rewrite removes substantial functionality including all tests, the publish command, and extensive Git/GitHub utilities, while introducing a new service-based architecture.

Key changes include:

  • Migration from imperative to Effect-based functional programming
  • Introduction of service layer pattern (GitService, GitHubService, WorkspaceService, etc.)
  • Complete removal of existing test suite
  • Removal of publish functionality
  • Simplified error handling using Effect's error types

Reviewed changes

Copilot reviewed 35 out of 37 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
src/index.ts New Effect-based API exposing verify, prepare, and publish (stub) methods
src/options.ts Normalized options configuration with Effect Context.Tag
src/verify.ts Rewritten verify command using Effect generators
src/utils/helpers.ts Helper functions for commit processing and global commit detection
src/services/git.service.ts Effect service wrapping Git operations
src/services/github.service.ts Effect service for GitHub API interactions
src/services/workspace.service.ts Effect service for workspace package discovery
src/services/version-calculator.service.ts Effect service for version bump calculations
src/services/dependency-graph.service.ts Effect service for dependency graph operations
src/services/package-updater.service.ts Effect service for updating package.json files
src/errors.ts Effect-based error types using Data.TaggedError
test/options.test.ts New tests for options normalization
test/helpers.test.ts New tests for helper functions
tsconfig.json Added Effect language service plugin
package.json Added Effect dependencies and updated imports
Multiple deleted files Removed old implementation files, tests, and documentation
Files not reviewed (1)
  • pnpm-lock.yaml: Language not supported

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

Simplified the handling of `packages` by removing type assertions in the `calculateBumps` and `topologicalOrder` function calls. This improves type safety and clarity in the code.
Copy link

@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: 17

🧹 Nitpick comments (20)
playground.ts (1)

13-18: Consider simplifying the error handling and return.

The catchAll logs and re-throws the same error, which adds little value. Also, return void 0 is unconventional.

-  yield* git.branches.checkout("main").pipe(Effect.catchAll((err) => {
-    console.error(`Error checking out main branch: ${err.message}`);
-    return Effect.fail(err);
-  }));
-
-  return void 0;
+  yield* git.branches.checkout("main").pipe(
+    Effect.tapError((err) => Effect.sync(() => console.error(`Error checking out main branch: ${err.message}`)))
+  );
src/verify.ts (1)

59-62: Incomplete implementation noted.

The TODO comments indicate remaining steps. Consider tracking these as issues or using Effect.todo to make the incomplete state explicit in the type system.

Would you like me to open an issue to track the remaining implementation steps (Steps 4-7)?

src/errors.ts (1)

26-47: Inconsistent use of readonly modifier on error properties.

The first five error classes use readonly for their properties, but WorkspaceError, GitHubError, VersionCalculationError, and OverridesLoadError don't. For consistency and immutability, consider adding readonly to all properties.

 export class WorkspaceError extends Data.TaggedError("WorkspaceError")<{
-  message: string;
-  operation?: string;
-  cause?: unknown;
+  readonly message: string;
+  readonly operation?: string;
+  readonly cause?: unknown;
 }> { }

 export class GitHubError extends Data.TaggedError("GitHubError")<{
-  message: string;
-  operation?: "getPullRequestByBranch" | "createPullRequest" | "updatePullRequest" | "setCommitStatus" | "request";
-  cause?: unknown;
+  readonly message: string;
+  readonly operation?: "getPullRequestByBranch" | "createPullRequest" | "updatePullRequest" | "setCommitStatus" | "request";
+  readonly cause?: unknown;
 }> { }

 export class VersionCalculationError extends Data.TaggedError("VersionCalculationError")<{
-  message: string;
-  packageName?: string;
-  cause?: unknown;
+  readonly message: string;
+  readonly packageName?: string;
+  readonly cause?: unknown;
 }> { }

 export class OverridesLoadError extends Data.TaggedError("OverridesLoadError")<{
-  message: string;
-  cause?: unknown;
+  readonly message: string;
+  readonly cause?: unknown;
 }> {}
src/services/git.service.ts (3)

43-51: Redundant .trim() call.

Line 50 has .map((line) => line.trim()) after line 49 already trims with .replace(/^\* /, "").trim().

     const listBranches = Effect.gen(function* () {
       const output = yield* execGitCommand(["branch", "--list"]);
       return output
         .trim()
         .split("\n")
         .filter((line) => line.length > 0)
-        .map((line) => line.replace(/^\* /, "").trim())
-        .map((line) => line.trim());
+        .map((line) => line.replace(/^\* /, "").trim());
     });

77-85: Use typed error instead of plain Error.

For consistency with the error handling pattern, use a typed error like WorkspaceError or GitError instead of a plain Error.

+import { GitError, GitCommandError, ExternalCommitParserError } from "../errors";
+// ... (GitError already imported, may need WorkspaceError)
+
     function stageChanges(files: readonly string[]) {
       return Effect.gen(function* () {
         if (files.length === 0) {
-          return yield* Effect.fail(new Error("No files to stage."));
+          return yield* Effect.fail(new GitError({ message: "No files to stage." }));
         }

         return yield* execGitCommand(["add", ...files]);
       });
     }

176-188: Use typed errors for workspace readiness checks.

GitNotRepositoryError and WorkspaceError are defined but not used here. This would enable better error handling downstream.

+import { GitNotRepositoryError, WorkspaceError } from "../errors";
+
     const assertWorkspaceReady = Effect.gen(function* () {
       const withinRepo = yield* isWithinRepository;
       if (!withinRepo) {
-        return yield* Effect.fail(new Error("Not within a Git repository."));
+        return yield* Effect.fail(new GitNotRepositoryError({ path: config.workspaceRoot }));
       }

       const clean = yield* isWorkingDirectoryClean;
       if (!clean) {
-        return yield* Effect.fail(new Error("Working directory is not clean."));
+        return yield* Effect.fail(new WorkspaceError({ 
+          message: "Working directory is not clean.",
+          operation: "assertWorkspaceReady"
+        }));
       }

       return true;
     });
src/utils/helpers.ts (1)

284-297: Redundant null checks after length check.

After checking pkg.commits.length === 0 and continuing, accessing pkg.commits[0] and pkg.commits[pkg.commits.length - 1] will always succeed. The subsequent null checks and throws are defensive but unreachable.

   for (const pkg of packages) {
     if (pkg.commits.length === 0) {
       continue;
     }

-    const firstCommit = pkg.commits[0];
-    if (!firstCommit) {
-      throw new Error(`No commits found for package ${pkg.name}`);
-    }
-
-    const lastCommit = pkg.commits[pkg.commits.length - 1];
-    if (!lastCommit) {
-      throw new Error(`No commits found for package ${pkg.name}`);
-    }
+    const firstCommit = pkg.commits[0]!;
+    const lastCommit = pkg.commits[pkg.commits.length - 1]!;
package.json (1)

46-47: Minor inconsistency in version pinning.

@effect/language-service uses a caret range (^0.60.0) while @effect/vitest uses exact pinning (0.27.0). Consider using consistent version pinning across Effect dev dependencies.

-    "@effect/language-service": "^0.60.0",
+    "@effect/language-service": "0.60.0",
test/options.test.ts (1)

28-31: Consider adding test for whitespace-only token.

The normalizeReleaseScriptsOptions function trims the token before validation, but there's no test verifying that a whitespace-only token is rejected.

it("throws if githubToken is whitespace only", () => {
  expect(() => normalizeReleaseScriptsOptions({ repo: "octocat/hello-world", githubToken: "   " })).toThrow();
});
src/services/version-calculator.service.ts (2)

14-16: Guard against undefined in BUMP_PRIORITY lookup.

If incoming or current contains an unexpected value not in BUMP_PRIORITY, the comparison will fail silently. Add a fallback or type assertion.

 function maxBump(current: BumpKind, incoming: BumpKind): BumpKind {
-  return BUMP_PRIORITY[incoming] > BUMP_PRIORITY[current] ? incoming : current;
+  const incomingPriority = BUMP_PRIORITY[incoming] ?? 0;
+  const currentPriority = BUMP_PRIORITY[current] ?? 0;
+  return incomingPriority > currentPriority ? incoming : current;
 }

37-91: Consider bounded concurrency for large workspaces.

Using { concurrency: "unbounded" } may cause resource exhaustion in large monorepos with many packages. Consider a reasonable bound or making it configurable.

-          { concurrency: "unbounded" },
+          { concurrency: 10 },
src/services/dependency-graph.service.ts (2)

55-56: Consider using a more efficient queue implementation.

queue.shift() is O(n) for arrays. For large dependency graphs, this could become a performance bottleneck. Consider using a proper queue structure or iterating with an index.

+          let queueIndex = 0;
           const ordered: PackageUpdateOrder[] = [];

-          while (queue.length > 0) {
-            const current = queue.shift()!;
+          while (queueIndex < queue.length) {
+            const current = queue[queueIndex++];

79-81: Consider providing more context in cycle detection error.

When a cycle is detected, identifying which packages are involved would help debugging.

           if (ordered.length !== packages.length) {
-            return yield* Effect.fail(new Error("Cycle detected in workspace dependencies"));
+            const processed = new Set(ordered.map(o => o.package.name));
+            const unprocessed = packages.filter(p => !processed.has(p.name)).map(p => p.name);
+            return yield* Effect.fail(new Error(`Cycle detected in workspace dependencies. Packages involved: ${unprocessed.join(", ")}`));
           }
src/options.ts (1)

4-6: DeepRequired type may not handle all cases correctly.

The conditional T[K] extends Required<T[K]> checks if the property is already required, but this doesn't correctly identify primitive types vs. objects that need deep transformation. Consider using a more robust implementation.

-type DeepRequired<T> = Required<{
-  [K in keyof T]: T[K] extends Required<T[K]> ? T[K] : DeepRequired<T[K]>
-}>;
+type DeepRequired<T> = T extends object
+  ? { [K in keyof T]-?: DeepRequired<T[K]> }
+  : T;
src/services/package-updater.service.ts (1)

57-62: Consider providing more context in file write errors.

The error is caught but loses context about which package failed. Wrap with additional information.

         return Effect.tryPromise({
           try: async () => {
             await fs.writeFile(fullPath, content, "utf8");
           },
-          catch: (e) => e as Error,
+          catch: (e) => new Error(`Failed to write ${fullPath}: ${(e as Error).message}`, { cause: e }),
         });
src/services/github.service.ts (1)

124-127: Unused dependency: GitService.Default.

GitService is imported and declared as a dependency but is never used within the GitHubService implementation. Consider removing it to avoid unnecessary coupling.

-import { GitService } from "./git.service.js";
-  dependencies: [
-    GitService.Default,
-  ],
+  dependencies: [],
src/index.ts (1)

43-46: Type assertion masks potential dependency issues.

The cast as Effect.Effect<A, E, never> suppresses type-checking for the requirements parameter R. If AppLayer doesn't fully satisfy a program's dependencies, this will cause runtime errors rather than compile-time errors.

Consider using Effect.runPromise with proper layer provision or defining AppLayer with the correct type to ensure compile-time safety. Alternatively, document why this cast is necessary.

src/services/workspace.service.ts (3)

80-101: Consider using @effect/platform FileSystem for consistency.

The service uses @effect/platform's CommandExecutor for running commands but uses Node.js fs/promises directly for file operations. For consistency and better testability, consider using @effect/platform's FileSystem service.


155-163: Dead code: excludedPackages is populated but never used.

The excludedPackages Set is created and populated but never read. Either remove it or use it for logging/debugging purposes.

-          const excludedPackages = new Set<string>();
...
-                    excludedPackages.add(rawProject.name);

243-243: Missing dependency declaration for CommandExecutor.

The service uses CommandExecutor.CommandExecutor on line 48 but doesn't declare it in the dependencies array. The service relies on the AppLayer in src/index.ts to provide this dependency, which works but makes the service less self-documenting and harder to test in isolation.

Consider declaring the dependency explicitly:

-  dependencies: [],
+  dependencies: [
+    NodeCommandExecutor.layer,
+  ],
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 71fb1a6 and 8c09ca7.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (36)
  • .vscode/settings.json (1 hunks)
  • HOW_IT_WORKS.md (0 hunks)
  • package.json (2 hunks)
  • playground.ts (1 hunks)
  • src/core/changelog.ts (0 hunks)
  • src/core/git.ts (0 hunks)
  • src/core/github.ts (0 hunks)
  • src/core/prompts.ts (0 hunks)
  • src/core/workspace.ts (0 hunks)
  • src/errors.ts (1 hunks)
  • src/index.ts (1 hunks)
  • src/options.ts (1 hunks)
  • src/publish.ts (0 hunks)
  • src/release.ts (0 hunks)
  • src/services/dependency-graph.service.ts (1 hunks)
  • src/services/git.service.ts (1 hunks)
  • src/services/github.service.ts (1 hunks)
  • src/services/package-updater.service.ts (1 hunks)
  • src/services/version-calculator.service.ts (1 hunks)
  • src/services/workspace.service.ts (1 hunks)
  • src/shared/options.ts (0 hunks)
  • src/shared/types.ts (0 hunks)
  • src/shared/utils.ts (0 hunks)
  • src/utils/helpers.ts (1 hunks)
  • src/verify.ts (1 hunks)
  • src/versioning/commits.ts (0 hunks)
  • src/versioning/package.ts (0 hunks)
  • src/versioning/version.ts (0 hunks)
  • test/_shared.ts (0 hunks)
  • test/core/changelog.authors.test.ts (0 hunks)
  • test/core/changelog.test.ts (0 hunks)
  • test/core/git.test.ts (0 hunks)
  • test/helpers.test.ts (1 hunks)
  • test/options.test.ts (1 hunks)
  • test/versioning/commits.test.ts (0 hunks)
  • tsconfig.json (1 hunks)
💤 Files with no reviewable changes (19)
  • src/publish.ts
  • src/versioning/commits.ts
  • test/core/changelog.authors.test.ts
  • test/versioning/commits.test.ts
  • HOW_IT_WORKS.md
  • src/core/workspace.ts
  • src/shared/types.ts
  • src/core/github.ts
  • src/core/git.ts
  • src/shared/utils.ts
  • src/versioning/version.ts
  • src/shared/options.ts
  • test/core/git.test.ts
  • src/core/changelog.ts
  • src/core/prompts.ts
  • test/core/changelog.test.ts
  • src/release.ts
  • test/_shared.ts
  • src/versioning/package.ts
🧰 Additional context used
🧬 Code graph analysis (10)
src/services/dependency-graph.service.ts (1)
src/services/workspace.service.ts (1)
  • WorkspacePackage (34-34)
test/helpers.test.ts (1)
src/utils/helpers.ts (4)
  • WorkspacePackageWithCommits (84-84)
  • isGlobalCommit (230-243)
  • isDependencyFile (262-270)
  • findCommitRange (280-315)
src/services/workspace.service.ts (1)
src/errors.ts (1)
  • WorkspaceError (26-30)
src/utils/helpers.ts (3)
src/services/git.service.ts (1)
  • GitService (8-219)
src/errors.ts (1)
  • OverridesLoadError (44-47)
src/services/workspace.service.ts (2)
  • WorkspacePackageSchema (25-32)
  • WorkspacePackage (34-34)
playground.ts (1)
src/services/git.service.ts (1)
  • GitService (8-219)
src/services/version-calculator.service.ts (2)
src/utils/helpers.ts (1)
  • WorkspacePackageWithCommits (84-84)
src/errors.ts (1)
  • VersionCalculationError (38-42)
test/options.test.ts (1)
src/options.ts (3)
  • ReleaseScriptsOptionsInput (8-35)
  • normalizeReleaseScriptsOptions (45-110)
  • ReleaseScriptsOptions (112-115)
src/services/git.service.ts (2)
src/options.ts (1)
  • ReleaseScriptsOptions (112-115)
src/errors.ts (2)
  • GitCommandError (8-11)
  • ExternalCommitParserError (13-16)
src/services/github.service.ts (2)
src/errors.ts (1)
  • GitHubError (32-36)
src/services/git.service.ts (1)
  • GitService (8-219)
src/services/package-updater.service.ts (1)
src/services/workspace.service.ts (1)
  • WorkspacePackage (34-34)
🪛 GitHub Actions: CI
src/services/dependency-graph.service.ts

[error] 1-1: tsc --noEmit failed: Cannot find module '../shared/types' or its corresponding type declarations. (TS2307)

🪛 GitHub Check: build
src/services/dependency-graph.service.ts

[failure] 1-1:
Cannot find module '../shared/types' or its corresponding type declarations.

src/services/workspace.service.ts

[failure] 7-7:
Module '"../options"' has no exported member 'ConfigOptions'.


[failure] 1-1:
Cannot find module '#shared/types' or its corresponding type declarations.

src/services/version-calculator.service.ts

[failure] 15-15:
Object is possibly 'undefined'.


[failure] 15-15:
Object is possibly 'undefined'.


[failure] 1-1:
Cannot find module '../shared/types' or its corresponding type declarations.

src/services/github.service.ts

[failure] 3-3:
Module '"../options.js"' has no exported member 'ConfigOptions'.

src/services/package-updater.service.ts

[failure] 6-6:
Module '"../options"' has no exported member 'ConfigOptions'.


[failure] 1-1:
Cannot find module '../shared/types' or its corresponding type declarations.

🔇 Additional comments (25)
playground.ts (1)

21-30: Layer composition and execution look correct.

The Effect layer composition follows standard patterns, providing dependencies in the correct order.

test/helpers.test.ts (5)

10-25: Well-designed test factory for creating mock commits.

The makeCommit helper creates realistic GitCommit objects with sensible defaults. Good use of hash padding for consistency.


27-46: Comprehensive package factory matching the schema.

The makePackage helper correctly constructs WorkspacePackageWithCommits objects with all required fields.


48-90: Thorough test coverage for isGlobalCommit.

Good coverage of edge cases including partial path prefix matching (line 82-85), empty inputs, and ./ prefix normalization.


92-160: Good coverage of dependency file detection.

Tests appropriately cover root-level files, nested paths, rejection of similar names, and edge cases.


162-283: Comprehensive findCommitRange test suite.

Good coverage of edge cases including empty packages, out-of-order commits, same timestamps, and timezone offsets. The same-timestamp test appropriately documents the iteration-order dependency.

src/verify.ts (1)

14-63: Effect-based program construction follows good patterns.

The function correctly separates program definition from layer provision, allowing callers to provide dependencies. The service wiring is clean.

src/errors.ts (1)

3-24: Well-structured tagged error classes.

Good use of Data.TaggedError with descriptive tags and contextual payloads. The specific error types will enable precise error handling with Effect's catchTag.

src/services/git.service.ts (1)

190-219: Clean API structure and service definition.

The API is well-organized with logical groupings. The as const assertion ensures type safety, and dependencies are correctly declared.

src/utils/helpers.ts (2)

53-84: Well-defined schemas for commits and packages.

The GitCommitSchema and WorkspacePackageWithCommitsSchema are properly structured with appropriate types and defaults.


230-270: Helper functions isGlobalCommit and isDependencyFile are well-implemented.

The path normalization and matching logic correctly handles edge cases like ./ prefixes and partial path matches.

.vscode/settings.json (1)

1-3: LGTM!

Standard VSCode configuration to use the workspace TypeScript version, which is required for the @effect/language-service plugin to function correctly.

tsconfig.json (1)

17-21: LGTM!

Adding the @effect/language-service plugin enhances IDE support for Effect-based code with better type inference and diagnostics.

package.json (1)

33-44: LGTM!

Effect ecosystem dependencies are properly added. The exact version pinning for runtime dependencies (@effect/platform, @effect/platform-node, effect) is good for reproducibility.

test/options.test.ts (2)

1-8: LGTM!

Clean imports and proper use of @effect/vitest for Effect-based testing.


72-102: LGTM on Effect DI tests.

Good use of it.effect and Effect.provideService to test the Context.Tag-based dependency injection pattern.

src/services/package-updater.service.ts (1)

8-18: LGTM on nextRange implementation.

Correctly handles workspace: prefix preservation and range specifiers (^, ~). The * and latest wildcards are properly preserved.

src/services/github.service.ts (2)

7-56: LGTM!

The schema definitions are well-structured and follow Effect's Schema patterns correctly. The use of Schema.NullOr for nullable fields and Schema.optional for optional fields is appropriate.


62-105: LGTM with a note on type safety.

The makeRequest helper is well-implemented with proper error handling, schema validation, and 204 response handling. The undefined as A cast on line 92 is a pragmatic workaround for endpoints that return no content.

src/index.ts (3)

48-59: LGTM!

The safeguard pattern validates workspace readiness during initialization, providing early failure with a clear error message. Good defensive programming.


121-140: LGTM!

The packages namespace provides clean delegations to WorkspaceService with appropriate null handling for missing packages.


32-41: Layer composition order is correct; services receive the configuration as expected.

The pattern Layer.succeed(ReleaseScriptsOptions, config).pipe(Layer.provide(GitService.Default), ...) is semantically correct. In Effect, A.pipe(Layer.provide(B)) builds layer B first, then uses its outputs to satisfy A's requirements. Here, ReleaseScriptsOptions is created without dependencies, then provided to services that require it (like GitService, which yields ReleaseScriptsOptions at line 11 of git.service.ts). The composition order is not inverted—dependencies flow correctly from the configuration through to dependent services.

Likely an incorrect or invalid review comment.

src/services/workspace.service.ts (3)

9-44: LGTM!

The schema definitions accurately model the package.json structure and pnpm workspace output. Good use of Schema.optional for fields that may not be present.


51-78: LGTM!

Good use of Effect.cached to memoize the expensive pnpm ls operation. The error handling properly wraps failures with operation context.


203-226: LGTM!

The filtering logic is clear and handles all cases correctly with appropriate early returns.

…ted interfaces

- Added `FindWorkspacePackagesOptions` interface to encapsulate package inclusion/exclusion logic.
- Updated `ReleaseScriptsOptionsInput` to utilize `FindWorkspacePackagesOptions` for better clarity and structure.
- Introduced `PackageUpdateOrder` interface with a new `level` property for dependency management.
- Refactored imports in various services to use `ReleaseScriptsOptions` instead of `ConfigOptions` for consistency.
- Added `PackageRelease` interface to define the structure of package updates, including versioning details and change indicators.
Copy link

@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

♻️ Duplicate comments (3)
src/options.ts (2)

69-72: Handle repos with multiple slashes more robustly.

The current implementation using split("/") with array destructuring will drop additional path segments if the repo string contains more than one slash (e.g., "org/group/repo"). This edge case should be handled as previously suggested.

Apply the previously suggested fix:

-  const [owner, repo] = fullRepo.split("/");
+  const [owner, ...repoParts] = fullRepo.split("/");
+  const repo = repoParts.join("/");
   if (!owner || !repo) {
     throw new Error(`Invalid repo format: "${fullRepo}". Expected format: "owner/repo" (e.g., "octocat/hello-world").`);
   }

47-111: User-provided types option is ignored.

The types parameter is destructured on line 48 but never used. The returned configuration always uses the hardcoded type mappings (lines 103-110), ignoring any custom types provided by the user.

Apply the previously suggested fix to merge user-provided types with defaults:

+  const defaultTypes = {
+    feat: { title: "🚀 Features" },
+    fix: { title: "🐞 Bug Fixes" },
+    refactor: { title: "🔧 Code Refactoring" },
+    perf: { title: "🏎 Performance" },
+    docs: { title: "📚 Documentation" },
+    style: { title: "🎨 Styles" },
+  };
+
   return {
     // ... other properties
-    types: {
-      feat: { title: "🚀 Features" },
-      fix: { title: "🐞 Bug Fixes" },
-      refactor: { title: "🔧 Code Refactoring" },
-      perf: { title: "🏎 Performance" },
-      docs: { title: "📚 Documentation" },
-      style: { title: "🎨 Styles" },
-    },
+    types: types ? { ...defaultTypes, ...types } : defaultTypes,
   };
src/services/github.service.ts (1)

106-117: URL construction bug: double /repos/ prefix.

The makeRequest helper on line 62 constructs URLs as https://api.github.com/repos/${config.owner}/${config.repo}/${endpoint}. On line 108, the endpoint includes /repos/${config.owner}/${config.repo}/ again, resulting in a malformed URL like:

https://api.github.com/repos/owner/repo/repos/owner/repo/pulls?...

This will return 404 errors.

Apply the previously suggested fix:

     function getPullRequestByBranch(branch: string) {
       const head = branch.includes(":") ? branch : `${config.owner}:${branch}`;
-      const url = `/repos/${config.owner}/${config.repo}/pulls?state=open&head=${encodeURIComponent(head)}`;
-      return makeRequest(url, Schema.Array(PullRequestSchema)).pipe(
+      return makeRequest(`pulls?state=open&head=${encodeURIComponent(head)}`, Schema.Array(PullRequestSchema)).pipe(
         Effect.map((pulls) => (pulls.length > 0 ? pulls[0] : null)),
🧹 Nitpick comments (2)
src/services/version-calculator.service.ts (2)

37-91: Consider bounded concurrency for large monorepos.

Using concurrency: "unbounded" may cause memory pressure or resource exhaustion in repositories with many packages (50+). Consider using a bounded concurrency value (e.g., 10 or 20) to limit parallel operations.

Apply this diff:

         ),
-        { concurrency: "unbounded" },
+        { concurrency: 20 },
       );

72-72: TODO: Interactive version prompts.

The comment indicates a planned feature for interactive version selection when prompts.versions is enabled. This is currently not implemented.

Would you like me to open a tracking issue for this feature, or help implement the interactive prompt logic?

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8c09ca7 and b6ac17c.

📒 Files selected for processing (6)
  • src/options.ts (1 hunks)
  • src/services/dependency-graph.service.ts (1 hunks)
  • src/services/github.service.ts (1 hunks)
  • src/services/package-updater.service.ts (1 hunks)
  • src/services/version-calculator.service.ts (1 hunks)
  • src/services/workspace.service.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/services/dependency-graph.service.ts
  • src/services/workspace.service.ts
🧰 Additional context used
🧬 Code graph analysis (3)
src/services/github.service.ts (3)
src/options.ts (1)
  • ReleaseScriptsOptions (114-117)
src/errors.ts (1)
  • GitHubError (32-36)
src/services/git.service.ts (1)
  • GitService (8-219)
src/services/version-calculator.service.ts (3)
src/services/package-updater.service.ts (2)
  • BumpKind (42-42)
  • PackageRelease (43-68)
src/utils/helpers.ts (1)
  • WorkspacePackageWithCommits (84-84)
src/errors.ts (1)
  • VersionCalculationError (38-42)
src/services/package-updater.service.ts (2)
src/services/workspace.service.ts (1)
  • WorkspacePackage (34-34)
src/options.ts (1)
  • ReleaseScriptsOptions (114-117)

…ate test cases

- Removed unnecessary type assertions in `createReleaseScripts` for better type safety.
- Updated test cases in `options.test.ts` to remove type assertions for `repo` parameter.
This update introduces a new `types` property to the `ReleaseScriptsOptionsInput` interface, allowing users to customize the types of changes in their release scripts. The default types are now merged with any user-defined types, providing greater flexibility in defining the structure of release notes.
Updated the `getMostRecentPackageTag` function to sort the tags in descending order by version using the `--sort=-version:refname` option. This change ensures that the most recent version tag is returned first, improving the accuracy of version retrieval for packages.
Updated the error handling in `WorkspaceService` to log a warning message when an invalid package is encountered, improving debugging and visibility into package processing issues.
Modified the `loadOverrides` function to pass the `options.sha` parameter to the `readFile` method, ensuring the correct version of the overrides file is read based on the specified SHA.
Refactor the `loadOverrides` function to streamline error handling when reading and parsing the overrides file. The new implementation uses `Effect.try` to directly handle JSON parsing errors, improving clarity and reducing unnecessary checks.
- Adjusted `mergePackageCommitsIntoPackages` to use `lastTag?.name` for fetching commits.
- Modified `mergeCommitsAffectingGloballyIntoPackage` to utilize `lastTag.sha.slice(0, 7)` for cutoff timestamp calculation.
Copy link

@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

♻️ Duplicate comments (4)
src/options.ts (1)

55-113: Nice: user types are now honored and merged with defaults.

The types: options.types ? { ...DEFAULT_TYPES, ...types } : DEFAULT_TYPES merge correctly lets callers override or extend the default sections without losing the base mapping, fixing the earlier issue where options.types was ignored.

src/utils/helpers.ts (1)

17-32: loadOverrides now reads from the correct git ref and simplifies error handling.

Switching to git.workspace.readFile(options.overridesPath, options.sha) and catching parse/read errors once, returning {} on failure, makes this helper much clearer and fixes the earlier bug where "utf8" was used as the ref.

src/services/git.service.ts (1)

104-113: Latest tag selection is reversed after adding --sort=-version:refname.

With git tag --list --sort=-version:refname, the newest tag is already first. Calling reverse()[0] now returns the oldest tag, so getMostRecentPackageTag will pick the wrong release and misdrive versioning logic.

You can drop the reverse() and take the first entry:

    function getMostRecentPackageTag(packageName: string) {
      return execGitCommand(["tag", "--list", "--sort=-version:refname", `${packageName}@*`]).pipe(
        Effect.map((tags) => {
          const tagList = tags
            .trim()
            .split("\n")
            .map((tag) => tag.trim())
            .filter((tag) => tag.length > 0);

-          return tagList.reverse()[0] || null;
+          return tagList[0] || null;
        }),
src/services/workspace.service.ts (1)

51-78: workspacePackageListOutput is executed eagerly and then treated as an Effect.

Using yield* here runs the pnpm ls effect during service construction and stores its result; later, findWorkspacePackages calls .pipe on that value, which will fail at type‑check/runtime.

You want a cached Effect instead:

-    const workspacePackageListOutput = yield* executor.string(Command.make("pnpm", "-r", "ls", "--json").pipe(
-      Command.workingDirectory(config.workspaceRoot),
-    )).pipe(
+    const workspacePackageListOutput = executor.string(Command.make("pnpm", "-r", "ls", "--json").pipe(
+      Command.workingDirectory(config.workspaceRoot),
+    )).pipe(
       Effect.flatMap((stdout) =>
         Effect.try({
           try: () => JSON.parse(stdout),
@@
       ),
-      Effect.cached,
-    );
+      Effect.cached,
+    );

This keeps workspacePackageListOutput as a lazily‑run, cached Effect that findWorkspacePackages can safely .pipe(...).

🧹 Nitpick comments (3)
src/services/workspace.service.ts (1)

151-204: Unused excludedPackages tracking.

excludedPackages is populated but never read, so it currently adds no value. Either log/report it or remove it to keep the implementation minimal:

-      return workspacePackageListOutput.pipe(
-        Effect.flatMap((rawProjects) => {
-          const allPackageNames = new Set<string>(rawProjects.map((p) => p.name));
-          const excludedPackages = new Set<string>();
+      return workspacePackageListOutput.pipe(
+        Effect.flatMap((rawProjects) => {
+          const allPackageNames = new Set<string>(rawProjects.map((p) => p.name));
@@
-                  if (!shouldIncludePackage(packageJson, options)) {
-                    excludedPackages.add(rawProject.name);
-                    return Effect.succeed(null);
-                  }
+                  if (!shouldIncludePackage(packageJson, options)) {
+                    return Effect.succeed(null);
+                  }
src/options.ts (1)

4-6: DeepRequired doesn’t actually remove undefined from nested fields.

The current definition mostly makes keys required but keeps value types like string | undefined unchanged, so NormalizedReleaseScriptsOptions isn’t truly “deeply required”. This can weaken type safety around config.branch, config.pullRequest, etc.

If you want a stricter normalized config type, consider a more conventional version:

type DeepRequired<T> = {
  [K in keyof T]-?: T[K] extends object
    ? DeepRequired<NonNullable<T[K]>>
    : NonNullable<T[K]>;
};

This keeps runtime behavior the same but makes the types better reflect that normalizeReleaseScriptsOptions fills all defaults.

src/index.ts (1)

43-46: Avoid as Effect.Effect<A, E, never> by tightening the environment type.

The cast in runProgram drops the real environment requirement, so if AppLayer ever stops providing something, TypeScript won’t catch it.

You can make this type‑safe by introducing an explicit environment type and constraining program:

type ReleaseScriptsEnv = Effect.Effect.Context<ReturnType<typeof AppLayer>>;
// or a manual intersection of the services you provide

const runProgram = <A>(
  program: Effect.Effect<A, unknown, ReleaseScriptsEnv>,
): Promise<A> => {
  const provided = program.pipe(Effect.provide(AppLayer));
  return Effect.runPromise(provided);
};

That way, missing services show up as type errors instead of being masked by as.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b6ac17c and 6a7827b.

📒 Files selected for processing (7)
  • src/index.ts (1 hunks)
  • src/options.ts (1 hunks)
  • src/services/git.service.ts (1 hunks)
  • src/services/github.service.ts (1 hunks)
  • src/services/workspace.service.ts (1 hunks)
  • src/utils/helpers.ts (1 hunks)
  • test/options.test.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • test/options.test.ts
🧰 Additional context used
🧬 Code graph analysis (4)
src/services/github.service.ts (3)
src/options.ts (1)
  • ReleaseScriptsOptions (116-119)
src/errors.ts (1)
  • GitHubError (32-36)
src/services/git.service.ts (1)
  • GitService (8-230)
src/services/git.service.ts (2)
src/options.ts (1)
  • ReleaseScriptsOptions (116-119)
src/errors.ts (2)
  • GitCommandError (8-11)
  • ExternalCommitParserError (13-16)
src/services/workspace.service.ts (2)
src/options.ts (2)
  • ReleaseScriptsOptions (116-119)
  • FindWorkspacePackagesOptions (8-12)
src/errors.ts (1)
  • WorkspaceError (26-30)
src/index.ts (7)
src/services/workspace.service.ts (2)
  • WorkspacePackage (34-34)
  • WorkspaceService (46-248)
src/options.ts (3)
  • ReleaseScriptsOptionsInput (14-37)
  • normalizeReleaseScriptsOptions (55-114)
  • ReleaseScriptsOptions (116-119)
src/services/git.service.ts (1)
  • GitService (8-230)
src/services/github.service.ts (1)
  • GitHubService (57-125)
src/services/dependency-graph.service.ts (1)
  • DependencyGraphService (9-97)
src/verify.ts (1)
  • constructVerifyProgram (14-64)
src/utils/helpers.ts (3)
  • loadOverrides (17-34)
  • mergePackageCommitsIntoPackages (69-96)
  • mergeCommitsAffectingGloballyIntoPackage (129-204)
🔇 Additional comments (3)
src/index.ts (1)

65-113: prepare() flow composes services coherently.

The prepare program reuses the verify‑like checks (PR existence, branch checkout), wires in overrides, discovers packages, merges commits (including global ones), computes bumps and dependency order, and finally calls packageUpdater.applyReleases. Structurally this is a solid Effect‑based orchestration and fits well with the new service layout.

src/services/github.service.ts (1)

61-103: makeRequest helper is a good centralization of GitHub API concerns.

The helper correctly builds the repo‑scoped URL, attaches auth and UA headers, handles 204 responses, and decodes via a provided schema while wrapping network and decode failures in GitHubError. This sets you up nicely for adding create/update/status endpoints later.

src/utils/helpers.ts (1)

69-96: mergePackageCommitsIntoPackages: good use of GitService and schema validation.

Fetching per‑package commits from the last tag to HEAD, enriching the workspace package, and validating via WorkspacePackageWithCommitsSchema is a clean way to bind Git data into your domain type. The explicit error message on decode failure will be helpful for debugging.

…fectingGloballyIntoPackage`

Updated the calculation of `cutoffTimestamp` to use the full SHA from `lastTag` instead of slicing it. This ensures accurate retrieval of commit timestamps for the most recent tag associated with a package.
…tingGloballyIntoPackage`

Replaced the use of `commit.shortHash` with `commit.hash` for accurate timestamp and affected files retrieval. This change ensures that the correct commit information is used when filtering commits that occurred after a package's last release.
Copy link

@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

♻️ Duplicate comments (3)
src/services/github.service.ts (1)

6-23: Pull request body field should allow null to handle valid API responses.

GitHub's REST API returns body: null for PRs without a description. The current Schema.String definition will fail decoding even though the response is valid. This pattern is already correctly used for mergeable on line 20.

 export const PullRequestSchema = Schema.Struct({
   number: Schema.Number,
   title: Schema.String,
-  body: Schema.String,
+  body: Schema.NullOr(Schema.String),
   head: Schema.Struct({
src/services/git.service.ts (1)

104-127: Tag sorting and selection logic is inconsistent.

The command uses --sort=-version:refname (descending order, newest first) but then calls .reverse()[0] which would return the oldest tag. This contradicts the likely intent to get the most recent tag.

     function getMostRecentPackageTag(packageName: string) {
       return execGitCommand(["tag", "--list", "--sort=-version:refname", `${packageName}@*`]).pipe(
         Effect.map((tags) => {
           const tagList = tags
             .trim()
             .split("\n")
             .map((tag) => tag.trim())
             .filter((tag) => tag.length > 0);

-          return tagList.reverse()[0] || null;
+          return tagList[0] || null;
         }),
src/utils/helpers.ts (1)

157-179: Hash key inconsistency between commitTimestamps and lookups.

commitTimestamps is keyed by shortHash (line 159), but lookups on lines 174 and 179 use commit.hash (full hash). This mismatch means lookups will fail and return undefined.

     // Used for quick lookup of commit timestamps/cutoffs
     const commitTimestamps = new Map(
-      allCommits.map((c) => [c.shortHash, new Date(c.date).getTime()]),
+      allCommits.map((c) => [c.hash, new Date(c.date).getTime()]),
     );

Alternatively, if shortHash is preferred, update lines 174 and 179 to use commit.shortHash. The key choice should be consistent throughout.

🧹 Nitpick comments (3)
src/services/git.service.ts (2)

43-51: Redundant .map((line) => line.trim()) call.

Line 50 trims lines that were already trimmed on line 49.

     const listBranches = Effect.gen(function* () {
       const output = yield* execGitCommand(["branch", "--list"]);
       return output
         .trim()
         .split("\n")
         .filter((line) => line.length > 0)
-        .map((line) => line.replace(/^\* /, "").trim())
-        .map((line) => line.trim());
+        .map((line) => line.replace(/^\* /, "").trim());
     });

187-199: Consider using custom tagged errors for workspace assertions.

The assertWorkspaceReady function uses generic Error instances. For consistency with the rest of the codebase's error handling pattern (using Data.TaggedError), consider defining specific error types.

src/utils/helpers.ts (1)

263-298: findCommitRange has defensive checks that can't trigger.

Lines 273-275 and 278-280 throw errors if firstCommit or lastCommit are falsy, but these cases are already guarded by the pkg.commits.length === 0 check on line 268. The throws are unreachable. Consider removing them or simplifying the logic.

   for (const pkg of packages) {
     if (pkg.commits.length === 0) {
       continue;
     }

-    const firstCommit = pkg.commits[0];
-    if (!firstCommit) {
-      throw new Error(`No commits found for package ${pkg.name}`);
-    }
-
-    const lastCommit = pkg.commits[pkg.commits.length - 1];
-    if (!lastCommit) {
-      throw new Error(`No commits found for package ${pkg.name}`);
-    }
+    const firstCommit = pkg.commits[0]!;
+    const lastCommit = pkg.commits[pkg.commits.length - 1]!;
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6a7827b and 623190b.

📒 Files selected for processing (3)
  • src/services/git.service.ts (1 hunks)
  • src/services/github.service.ts (1 hunks)
  • src/utils/helpers.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
src/services/git.service.ts (2)
src/options.ts (1)
  • ReleaseScriptsOptions (116-119)
src/errors.ts (2)
  • GitCommandError (8-11)
  • ExternalCommitParserError (13-16)
src/services/github.service.ts (3)
src/options.ts (1)
  • ReleaseScriptsOptions (116-119)
src/errors.ts (1)
  • GitHubError (32-36)
src/services/git.service.ts (1)
  • GitService (8-230)
🔇 Additional comments (16)
src/services/git.service.ts (6)

1-6: Imports look correct and minimal.

The imports align with the service's responsibilities: command execution from @effect/platform, commit parsing, and custom error types.


8-23: Service setup and error mapping look good.

The Effect.Service pattern is correctly implemented with proper dependency injection of CommandExecutor and ReleaseScriptsOptions. Error mapping to GitCommandError preserves command context.


25-34: Dry-run helper design is sound.

The conditional assignment pattern cleanly separates dry-run behavior. The comment clarifies intent well.


129-146: External commit parser integration is well-wrapped.

The Effect.tryPromise pattern correctly bridges the async library call, and errors are wrapped in the custom ExternalCommitParserError type with preserved cause information.


148-185: File-to-commit mapping implementation is solid.

The parsing logic correctly handles git log output format, filters empty lines, and associates files with their respective commit hashes. The regex for SHA detection is appropriate.


201-230: API surface is well-organized.

The grouped namespace structure (branches, commits, tags, workspace) provides a clear and discoverable API. Dependencies are correctly declared.

src/services/github.service.ts (3)

25-55: Schemas and type exports are well-defined.

The schema definitions for PR options, commit status, and repository info are appropriate. The derived type exports provide good TypeScript integration.


61-104: The makeRequest helper is well-implemented.

Good error handling with proper status checking, 204 handling, and schema validation. The error wrapping into GitHubError with operation metadata is consistent.


106-116: getPullRequestByBranch implementation looks correct.

The head formatting logic and error mapping are appropriate. The null coalescing for empty results is handled correctly.

src/utils/helpers.ts (7)

1-10: Imports and interface definition look good.

The imports are appropriate for the helper utilities, and VersionOverrides provides a clear type for the overrides map.


17-34: loadOverrides implementation is clean and handles errors gracefully.

The function correctly uses GitService, reads from the specified ref, parses JSON, and falls back to an empty object on any error. This is a good resilient pattern.


36-66: Commit and package schemas are well-structured.

The GitCommitSchema captures all relevant commit metadata, and WorkspacePackageWithCommitsSchema properly extends the base schema with commit arrays.


69-96: mergePackageCommitsIntoPackages implementation is correct.

The function properly retrieves the most recent tag per package, fetches commits since that tag, and validates the result against the schema.


165-168: Potential hash format mismatch for tag SHA lookup.

lastTag.sha from getMostRecentPackageTag returns the full SHA (via git rev-parse). If you standardize commitTimestamps on full hashes (as suggested above), this lookup will work correctly. Verify that lastTag.sha format matches the chosen key format.


213-226: isGlobalCommit logic is correct.

The function properly normalizes paths and checks whether files fall outside all package directories.


245-253: isDependencyFile may have false positives.

The check normalized.endsWith(/${dep}) would match any path ending with the dependency filename, including nested directories like some/deep/path/package.json. This is likely intentional for monorepo structures, but worth confirming.

Refactor the `writeCommit` and `pushChanges` functions to directly return the result of `execGitCommandIfNotDry`, removing unnecessary generator functions. This change improves code readability and maintains the existing functionality.
… ranges

Updated the `nextRange` function to check if the new version satisfies existing complex ranges using `semver.satisfies`. If the new version is outside the existing range, an error is thrown to prevent silent failures. This change lays the groundwork for future implementation of range updating logic.
…aceService`

- Moved the `writePackageJson` function to `WorkspaceService` for better modularity.
- Updated `PackageUpdaterService` to utilize the new `writePackageJson` method, improving code organization and maintainability.
Improves the `maxBump` function by explicitly calculating the priorities of `incoming` and `current` bump types. This change enhances readability and maintainability of the code.
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

Copilot reviewed 34 out of 36 changed files in this pull request and generated 5 comments.

Files not reviewed (1)
  • pnpm-lock.yaml: Language not supported

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

Comment on lines +83 to +108
const currentBranch = yield* git.branches.get;
if (currentBranch !== config.branch.default) {
yield* git.branches.checkout(config.branch.default);
yield* Console.log(`✅ Checked out to default branch "${config.branch.default}".`);
}

const overrides = yield* loadOverrides({
sha: releasePullRequest.head.sha,
overridesPath: ".github/ucdjs-release.overrides.json",
});

yield* Console.log("Loaded overrides:", overrides);

const packages = (yield* workspace.discoverWorkspacePackages.pipe(
Effect.flatMap(mergePackageCommitsIntoPackages),
Effect.flatMap((pkgs) => mergeCommitsAffectingGloballyIntoPackage(pkgs, config.globalCommitMode)),
));

yield* Console.log("Discovered packages with commits and global commits:", packages);

const releases = yield* versionCalculator.calculateBumps(packages, overrides);
const ordered = yield* dependencyGraph.topologicalOrder(packages);

yield* Console.log("Calculated releases:", releases);
yield* Console.log("Release order:", ordered);

Copy link

Copilot AI Dec 8, 2025

Choose a reason for hiding this comment

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

The verify and prepare programs have identical structure and duplicated code. Both checkout the default branch, load overrides, discover packages with commits, and calculate releases. Consider extracting this shared logic into a common helper function to reduce duplication and ensure consistency.

Suggested change
const currentBranch = yield* git.branches.get;
if (currentBranch !== config.branch.default) {
yield* git.branches.checkout(config.branch.default);
yield* Console.log(`✅ Checked out to default branch "${config.branch.default}".`);
}
const overrides = yield* loadOverrides({
sha: releasePullRequest.head.sha,
overridesPath: ".github/ucdjs-release.overrides.json",
});
yield* Console.log("Loaded overrides:", overrides);
const packages = (yield* workspace.discoverWorkspacePackages.pipe(
Effect.flatMap(mergePackageCommitsIntoPackages),
Effect.flatMap((pkgs) => mergeCommitsAffectingGloballyIntoPackage(pkgs, config.globalCommitMode)),
));
yield* Console.log("Discovered packages with commits and global commits:", packages);
const releases = yield* versionCalculator.calculateBumps(packages, overrides);
const ordered = yield* dependencyGraph.topologicalOrder(packages);
yield* Console.log("Calculated releases:", releases);
yield* Console.log("Release order:", ordered);
// Use shared helper to resolve release state
const { packages, releases, ordered, overrides } = yield* resolveReleaseState({
git,
workspace,
versionCalculator,
dependencyGraph,
config,
sha: releasePullRequest.head.sha,
});

Copilot uses AI. Check for mistakes.
…ctor queue processing

Refactor the queue processing in `topologicalOrder` to use an index instead of shifting elements, enhancing performance. Additionally, improve the error message for cycle detection to include the names of unprocessed packages, providing better context for debugging.
Changed the commit timestamp lookup in `mergeCommitsAffectingGloballyIntoPackage` to utilize the full `hash` instead of `shortHash`. This improves the accuracy of commit identification during the merge process.
@luxass luxass merged commit f3ed313 into main Dec 8, 2025
2 checks passed
@luxass luxass deleted the effect-rewrite branch December 8, 2025 01:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants