Skip to content

Conversation

@Huijiro
Copy link
Member

@Huijiro Huijiro commented Aug 25, 2025

Probably needs more updates on the deployment side for fully work.

Summary by CodeRabbit

  • New Features

    • Project initialization now auto-detects JavaScript runtime (nodejs or bunjs) from lockfiles.
  • Documentation

    • Runtime/provider docs updated to reflect supported runtimes: nodejs, bunjs, and uv for Python.
  • Refactor

    • Unified JavaScript install/bundling flow with CI-aware install flags, improved error reporting, and bun lockfile handling; Python packaging narrowed to uv.
  • Tests

    • Added tests for JavaScript package-manager detection and PNPM CI behavior.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Aug 25, 2025

Caution

Review failed

The pull request is closed.

Walkthrough

Add lockfile-based JS runtime detection during project init; centralize JS package-manager detection and CI-aware install command construction in the bundler; restrict supported runtimes to "bunjs", "nodejs", and "uv"; add unit tests for package-manager detection and PNPM CI flags; trivial schema newline update.

Changes

Cohort / File(s) Summary
Schema file
agentuity.schema.json
Trailing newline added; schema metadata updated to reflect bundler.runtime values ("bunjs", "nodejs", "uv").
Project init / CLI
cmd/project.go
Add detectRuntime(dir string, defaultRuntime string) to auto-detect JS runtime from lockfiles (bun preferred, then pnpm/yarn/npm -> nodejs); integrate detection into project initialization; non-JS defaults bypass detection.
Bundler core
internal/bundler/bundler.go
Add detectPackageManager, jsInstallCommandSpec, getJSInstallCommand, generateBunLockfile, and applyCIModifications; refactor bundleJavascript to use these helpers; implement CI-aware flag adjustments for pnpm and bun; execute install commands with improved logging and error handling; bun lockfile generation when appropriate.
Dev runner (docs/comment)
internal/dev/dev.go
Update comment to mention "nodejs and pnpm" for source-map guidance; no behavioral change.
Template metadata
internal/mcp/project.go
Update JSON schema description for Provider to list bunjs, nodejs, or uv.
Runtime validation
internal/project/project.go
Remove deno from allowed JS runtimes; restrict Python runtime options to uv; update validation error messages.
Tests
internal/bundler/bundler_test.go
Add TestJavaScriptPackageManagerDetection (table-driven lockfile scenarios) and TestPnpmCIFlags to validate package-manager detection and CI flag behavior; create temp lockfiles and assert command construction.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant U as User
  participant CLI as cmd/project.go
  participant FS as Filesystem
  participant Cfg as Project Config

  U->>CLI: init project (with Provider.Runtime default)
  CLI->>FS: inspect lockfiles (bun.lockb/bun.lock -> pnpm-lock.yaml -> package-lock.json -> yarn.lock)
  alt Bun lockfile found and default supports JS
    CLI->>Cfg: set Bundler.Runtime = "bunjs"
  else pnpm lockfile found and default supports JS
    CLI->>Cfg: set Bundler.Runtime = "nodejs" (pnpm)
  else npm/yarn lockfile found and default supports JS
    CLI->>Cfg: set Bundler.Runtime = "nodejs"
  else None or non‑JS default
    CLI->>Cfg: set Bundler.Runtime = defaultRuntime
  end
Loading
sequenceDiagram
  autonumber
  participant Bundler as internal/bundler
  participant FS as Filesystem
  participant CI as CI/Env
  participant PM as Package Manager

  Bundler->>Bundler: bundleJavascript(runtime, projectDir)
  Bundler->>FS: detectPackageManager(projectDir) (pnpm/bun/yarn/npm)
  alt runtime == bunjs
    Bundler->>Bundler: generateBunLockfile (if logger present)
    Bundler->>CI: check CI
    Bundler->>PM: run `bun install` with CI-aware args
  else runtime == nodejs and pnpm lock
    Bundler->>CI: check CI
    Bundler->>PM: run `pnpm install` (CI vs non-CI args)
  else runtime == nodejs and yarn lock
    Bundler->>PM: run `yarn install --frozen-lockfile`
  else runtime == nodejs (default)
    Bundler->>PM: run `npm install` with production flags
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • Changes to make teams work #432: touches internal/bundler/bundler.go and modifies bundleJavascript logging/flow; likely overlap with the bundler refactor here.

Suggested reviewers

  • pec1985
  • robindiddams
  • jhaynie

Poem

I nibble lockfiles in the night,
I sniff for bun, pnpm, yarny light.
CI flags hum, installs align,
runtimes set and tests all fine.
A rabbit hops — the build feels right. 🥕


📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 69c0be3 and 3c333eb.

📒 Files selected for processing (1)
  • internal/bundler/bundler.go (2 hunks)
✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch grodrigues/agent-545-add-pnpm-support

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

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: 4

Caution

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

⚠️ Outside diff range comments (1)
internal/dev/dev.go (1)

128-138: NODE_OPTIONS is appended without a key (env var not set).

The env slice requires entries of the form KEY=VALUE. Appending just --enable-source-maps won't set NODE_OPTIONS.

Apply this diff:

-  projectServerCmd.Env = append(projectServerCmd.Env, nodeOptions)
+  projectServerCmd.Env = append(projectServerCmd.Env, fmt.Sprintf("NODE_OPTIONS=%s", nodeOptions))

Optional: avoid duplicating the flag if it already exists and keep imports tidy. I can push a follow-up change to add strings.Contains(nodeOptions, "--enable-source-maps") and import strings if you want.

🧹 Nitpick comments (2)
cmd/project.go (2)

41-71: Prefer package.json “packageManager” and cover npm-shrinkwrap.json to make detection more robust.

Two low-risk tweaks improve fidelity without changing the overall intent:

  • Read package.json’s packageManager (e.g., "pnpm@9", "yarn@4", "npm@10", "bun@1") and honor it first.
  • Treat npm-shrinkwrap.json as npm (same lock semantics as package-lock.json).

Apply this diff within detectPackageManager:

 func detectPackageManager(dir string, defaultRuntime string) string {
   // Only auto-detect for JavaScript-based runtimes
   if defaultRuntime != "nodejs" && defaultRuntime != "bunjs" && defaultRuntime != "pnpm" {
     return defaultRuntime
   }
 
+  // Prefer explicit packageManager declared in package.json if present.
+  if b, err := os.ReadFile(filepath.Join(dir, "package.json")); err == nil {
+    var pkg struct{ PackageManager string `json:"packageManager"` }
+    if json.Unmarshal(b, &pkg) == nil && pkg.PackageManager != "" {
+      name := pkg.PackageManager
+      if i := strings.IndexRune(name, '@'); i > 0 {
+        name = name[:i]
+      }
+      switch name {
+      case "pnpm":
+        return "pnpm"
+      case "bun":
+        return "bunjs"
+      case "yarn", "npm":
+        return "nodejs"
+      }
+    }
+  }
+
   // Check for pnpm lockfile first (most specific)
   if util.Exists(filepath.Join(dir, "pnpm-lock.yaml")) {
     return "pnpm"
   }
 
   // Check for bun lockfiles
   if util.Exists(filepath.Join(dir, "bun.lockb")) || util.Exists(filepath.Join(dir, "bun.lock")) {
     return "bunjs"
   }
 
   // Check for npm lockfile
-  if util.Exists(filepath.Join(dir, "package-lock.json")) {
+  if util.Exists(filepath.Join(dir, "package-lock.json")) || util.Exists(filepath.Join(dir, "npm-shrinkwrap.json")) {
     return "nodejs"
   }
 
   // Check for yarn lockfile (fallback to nodejs for yarn projects)
   if util.Exists(filepath.Join(dir, "yarn.lock")) {
     return "nodejs"
   }
 
   // No lockfile found, use the template default
   return defaultRuntime
 }

And add the missing import used above:

// add to imports at the top of this file
import "strings"

41-71: Add table-driven unit tests for detection to prevent regressions.

Recommend covering: explicit packageManager, each lockfile, no lockfile, and non-JS default runtime.

Here’s a minimal test you can drop in cmd/project_test.go:

package cmd

import (
	"os"
	"path/filepath"
	"testing"
)

func write(t *testing.T, dir, name, body string) {
	t.Helper()
	fn := filepath.Join(dir, name)
	if err := os.WriteFile(fn, []byte(body), 0600); err != nil {
		t.Fatalf("write %s: %v", fn, err)
	}
}

func TestDetectPackageManager(t *testing.T) {
	t.Run("non-js default passthrough", func(t *testing.T) {
		tmp := t.TempDir()
		if got := detectPackageManager(tmp, "deno"); got != "deno" {
			t.Fatalf("got %s, want deno", got)
		}
	})

	t.Run("packageManager pnpm", func(t *testing.T) {
		tmp := t.TempDir()
		write(t, tmp, "package.json", `{"packageManager":"pnpm@9.0.0"}`)
		if got := detectPackageManager(tmp, "nodejs"); got != "pnpm" {
			t.Fatalf("got %s, want pnpm", got)
		}
	})

	t.Run("pnpm lock", func(t *testing.T) {
		tmp := t.TempDir()
		write(t, tmp, "pnpm-lock.yaml", "")
		if got := detectPackageManager(tmp, "nodejs"); got != "pnpm" {
			t.Fatalf("got %s, want pnpm", got)
		}
	})

	t.Run("bun lock", func(t *testing.T) {
		tmp := t.TempDir()
		write(t, tmp, "bun.lockb", "")
		if got := detectPackageManager(tmp, "pnpm"); got != "bunjs" {
			t.Fatalf("got %s, want bunjs", got)
		}
	})

	t.Run("npm lock and shrinkwrap", func(t *testing.T) {
		tmp := t.TempDir()
		write(t, tmp, "npm-shrinkwrap.json", "{}")
		if got := detectPackageManager(tmp, "pnpm"); got != "nodejs" {
			t.Fatalf("got %s, want nodejs", got)
		}
	})

	t.Run("yarn lock maps to nodejs", func(t *testing.T) {
		tmp := t.TempDir()
		write(t, tmp, "yarn.lock", "")
		if got := detectPackageManager(tmp, "bunjs"); got != "nodejs" {
			t.Fatalf("got %s, want nodejs", got)
		}
	})

	t.Run("no hints falls back to default", func(t *testing.T) {
		tmp := t.TempDir()
		if got := detectPackageManager(tmp, "pnpm"); got != "pnpm" {
			t.Fatalf("got %s, want pnpm", got)
		}
	})
}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 606bbe1 and 4855da4.

📒 Files selected for processing (6)
  • agentuity.schema.json (1 hunks)
  • cmd/project.go (2 hunks)
  • internal/bundler/bundler.go (1 hunks)
  • internal/dev/dev.go (1 hunks)
  • internal/mcp/project.go (1 hunks)
  • internal/project/project.go (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
internal/dev/dev.go (1)
internal/project/project.go (2)
  • Project (145-154)
  • Bundler (481-490)
cmd/project.go (1)
internal/util/io.go (1)
  • Exists (14-19)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Analyze (go)
🔇 Additional comments (3)
internal/mcp/project.go (1)

26-26: LGTM — provider description updated to include pnpm.

Doc-only change looks good and aligns with added pnpm support.

cmd/project.go (2)

41-47: Sensible scope limiting of auto-detect to JS runtimes — LGTM.

Early return for non-JS runtimes avoids surprising overrides and keeps behavior predictable for Python/Deno templates.


175-175: Good integration: runtime now reflects on-disk reality.

Using detectPackageManager here ensures the stored runtime matches the scaffolded project. No further action needed.

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)
internal/bundler/bundler.go (1)

146-171: Extract install command selection for testability; then call it here

The install selection logic is embedded in bundleJavascript, making it hard to test without invoking esbuild, typecheck, etc. Extract a pure function and use it both here and in tests. This will also fix the current tests being tautological.

Apply this focused change here to call the extractor:

-		var install *exec.Cmd
-		switch theproject.Bundler.Runtime {
-		case "nodejs":
-			if util.Exists(filepath.Join(dir, "yarn.lock")) {
-				install = exec.CommandContext(ctx.Context, "yarn", "install", "--frozen-lockfile")
-			} else {
-				install = exec.CommandContext(ctx.Context, "npm", "install", "--no-audit", "--no-fund", "--include=prod", "--ignore-scripts")
-			}
-		case "bunjs":
-			args := []string{"install", "--production", "--no-save", "--ignore-scripts"}
-			if ctx.CI {
-				args = append(args, "--verbose", "--no-cache")
-			} else {
-				args = append(args, "--no-progress", "--no-summary", "--silent")
-			}
-			install = exec.CommandContext(ctx.Context, "bun", args...)
-		case "pnpm":
-			args := []string{"install", "--prod", "--ignore-scripts"}
-			if ctx.CI {
-				args = append(args, "--reporter=append-only", "--frozen-lockfile")
-			} else {
-				args = append(args, "--silent")
-			}
-			install = exec.CommandContext(ctx.Context, "pnpm", args...)
-		default:
-			return fmt.Errorf("unsupported runtime: %s", theproject.Bundler.Runtime)
-		}
+		cmdName, args, err := jsInstallCommandSpec(ctx, dir, theproject.Bundler.Runtime)
+		if err != nil {
+			return err
+		}
+		install := exec.CommandContext(ctx.Context, cmdName, args...)

And add this helper (outside this hunk; place near bundleJavascript):

// jsInstallCommandSpec returns the package manager binary and arguments for the given runtime/dir/CI.
func jsInstallCommandSpec(ctx BundleContext, dir, runtime string) (string, []string, error) {
	switch runtime {
	case "nodejs":
		if util.Exists(filepath.Join(dir, "yarn.lock")) {
			return "yarn", []string{"install", "--frozen-lockfile"}, nil
		}
		if ctx.CI && util.Exists(filepath.Join(dir, "package-lock.json")) {
			return "npm", []string{"ci", "--omit=dev", "--ignore-scripts", "--no-audit", "--no-fund"}, nil
		}
		return "npm", []string{"install", "--omit=dev", "--ignore-scripts", "--no-audit", "--no-fund"}, nil
	case "bunjs":
		args := []string{"install", "--production", "--ignore-scripts"}
		// --no-save is unnecessary for install; omit to reduce noise.
		if ctx.CI {
			args = append(args, "--verbose", "--no-cache")
		} else {
			args = append(args, "--no-progress", "--no-summary", "--silent")
		}
		return "bun", args, nil
	case "pnpm":
		args := []string{"install", "--prod", "--ignore-scripts"}
		if ctx.CI {
			args = append(args, "--frozen-lockfile", "--reporter=append-only")
		} else {
			args = append(args, "--silent")
		}
		return "pnpm", args, nil
	default:
		return "", nil, fmt.Errorf("unsupported runtime: %s", runtime)
	}
}

I can push a commit wiring this extraction and updating the tests if you’d like.

🧹 Nitpick comments (3)
internal/bundler/bundler.go (3)

150-154: NodeJS: prefer reproducible npm installs in CI and use omit=dev instead of include=prod

Current fallback always uses “npm install … --include=prod”. In CI, “npm ci” is reproducible and will fail on lock drift; outside CI, “npm install … --omit=dev” is more idiomatic than “--include=prod”.

Apply this change:

-			} else {
-				install = exec.CommandContext(ctx.Context, "npm", "install", "--no-audit", "--no-fund", "--include=prod", "--ignore-scripts")
-			}
+			} else {
+				if ctx.CI && util.Exists(filepath.Join(dir, "package-lock.json")) {
+					install = exec.CommandContext(ctx.Context, "npm", "ci", "--omit=dev", "--ignore-scripts", "--no-audit", "--no-fund")
+				} else {
+					install = exec.CommandContext(ctx.Context, "npm", "install", "--omit=dev", "--ignore-scripts", "--no-audit", "--no-fund")
+				}
+			}

Note: npm ci requires package-lock.json; the guard avoids failures when it’s absent.


150-152: Yarn flag compatibility across Yarn v1 vs Berry

“--frozen-lockfile” is correct for Yarn v1; Yarn Berry prefers “--immutable” (and optionally “--immutable-cache”). Optional: detect Berry (e.g., .yarnrc.yml with yarnPath) and switch flags, or append both flags to be tolerant.

Would you like me to wire a lightweight detector and test for it?


156-163: Bun install flags: verify necessity of --no-save on bun install

“--no-save” is primarily relevant for add; install doesn’t update manifests. Keeping it is harmless if bun tolerates it, but consider removing to reduce noise. Otherwise LGTM on CI vs non-CI toggles.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 4855da4 and 5f4e77d.

📒 Files selected for processing (4)
  • agentuity.schema.json (1 hunks)
  • internal/bundler/bundler.go (2 hunks)
  • internal/bundler/bundler_test.go (2 hunks)
  • internal/project/project.go (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • internal/project/project.go
  • agentuity.schema.json
🧰 Additional context used
🧬 Code graph analysis (1)
internal/bundler/bundler.go (1)
internal/util/io.go (1)
  • Exists (14-19)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Analyze (go)
🔇 Additional comments (1)
internal/bundler/bundler.go (1)

163-171: PNPM flags LGTM and align with previous review feedback

CI: append-only reporter and frozen-lockfile; non-CI: silent. This replaces the invalid “--reporter=silent” and adds reproducibility in CI.

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: 0

♻️ Duplicate comments (1)
internal/bundler/bundler.go (1)

181-190: pnpm flags LGTM; previous reporter/silent issues are resolved.

Non-CI uses --silent; CI switches to --reporter=append-only and --frozen-lockfile. This matches pnpm best practices and avoids interleaved output.

🧹 Nitpick comments (5)
internal/bundler/bundler.go (5)

144-161: Prefer npm --omit=dev (or npm ci) and recognize Yarn Berry immutability.

  • The npm flag --include=prod is brittle and not consistently supported across npm versions. Prefer --omit=dev (or --production) to exclude dev deps.
  • Yarn Berry (v2+) uses --immutable instead of --frozen-lockfile. You can cheaply detect Berry via .yarnrc.yml or .yarn/releases.

Apply this diff to harden installs (base spec remains CI-agnostic):

-func jsInstallCommandSpec(ctx context.Context, projectDir, runtime string) (string, []string, error) {
+func jsInstallCommandSpec(ctx context.Context, projectDir, runtime string) (string, []string, error) {
   switch runtime {
   case "nodejs":
-    if util.Exists(filepath.Join(projectDir, "yarn.lock")) {
-      return "yarn", []string{"install", "--frozen-lockfile"}, nil
-    } else {
-      return "npm", []string{"install", "--no-audit", "--no-fund", "--include=prod", "--ignore-scripts"}, nil
-    }
+    // Prefer Yarn when a yarn.lock is present; detect Berry immutability
+    if util.Exists(filepath.Join(projectDir, ".yarnrc.yml")) || util.Exists(filepath.Join(projectDir, ".yarn", "releases")) {
+      return "yarn", []string{"install", "--immutable"}, nil
+    }
+    if util.Exists(filepath.Join(projectDir, "yarn.lock")) {
+      return "yarn", []string{"install", "--frozen-lockfile"}, nil
+    }
+    // npm path
+    return "npm", []string{"install", "--omit=dev", "--ignore-scripts", "--no-audit", "--no-fund"}, nil
   case "bunjs":
     return "bun", []string{"install", "--production", "--no-save", "--ignore-scripts", "--no-progress", "--no-summary", "--silent"}, nil
   case "pnpm":
     return "pnpm", []string{"install", "--prod", "--ignore-scripts", "--silent"}, nil
   default:
     return "", nil, fmt.Errorf("unsupported runtime: %s", runtime)
   }
 }

If you want npm ci only in CI to avoid cleaning local node_modules, see my CI suggestion in a later comment.
Would you like me to add tests for Yarn Berry detection and npm flag behavior?


144-146: Nit: Unused ctx parameter; rename to underscore for clarity.

The ctx context.Context parameter isn’t used. Rename to _ to signal intent and avoid confusion with the outer BundleContext.

-func jsInstallCommandSpec(ctx context.Context, projectDir, runtime string) (string, []string, error) {
+func jsInstallCommandSpec(_ context.Context, projectDir, runtime string) (string, []string, error) {

163-194: Avoid mutating a slice while ranging; current i-- is a no-op.

In both loops you modify args while iterating with range. That’s safe here because range iterates over a snapshot, but the i-- has no effect and the code is harder to reason about. Prefer filtering into a new slice for clarity and correctness.

   if ctx.CI {
     if runtime == "bunjs" {
-      // Replace silent flags with verbose for CI
-      for i, arg := range args {
-        if arg == "--no-progress" || arg == "--no-summary" || arg == "--silent" {
-          args = append(args[:i], args[i+1:]...)
-          i--
-        }
-      }
+      // Replace silent flags with verbose for CI
+      drop := map[string]struct{}{"--no-progress": {}, "--no-summary": {}, "--silent": {}}
+      filtered := make([]string, 0, len(args))
+      for _, a := range args {
+        if _, rm := drop[a]; !rm {
+          filtered = append(filtered, a)
+        }
+      }
+      args = filtered
       args = append(args, "--verbose", "--no-cache")
     } else if runtime == "pnpm" {
       // Remove silent flag and add CI-specific flags
-      for i, arg := range args {
-        if arg == "--silent" {
-          args = append(args[:i], args[i+1:]...)
-          break
-        }
-      }
+      for i := 0; i < len(args); i++ {
+        if args[i] == "--silent" {
+          args = append(args[:i], args[i+1:]...)
+          break
+        }
+      }
       args = append(args, "--reporter=append-only", "--frozen-lockfile")
     }
   }

163-194: Make npm installs deterministic in CI (use npm ci when lockfile exists).

For the nodejs runtime path where npm is selected, CI runs benefit from npm ci for speed and reproducibility.

 func getJSInstallCommand(ctx BundleContext, projectDir, runtime string) (string, []string, error) {
   cmd, args, err := jsInstallCommandSpec(ctx.Context, projectDir, runtime)
   if err != nil {
     return "", nil, err
   }
 
   // Apply CI-specific modifications
   if ctx.CI {
     if runtime == "bunjs" {
       ...
-    } else if runtime == "pnpm" {
+    } else if runtime == "pnpm" {
       ...
+    } else if runtime == "nodejs" && cmd == "npm" {
+      // Prefer deterministic npm installs in CI
+      if util.Exists(filepath.Join(projectDir, "package-lock.json")) {
+        args = []string{"ci", "--omit=dev", "--ignore-scripts", "--no-audit", "--no-fund"}
+      } else {
+        // Fall back to install, still omit dev deps
+        args = []string{"install", "--omit=dev", "--ignore-scripts", "--no-audit", "--no-fund"}
+      }
     }
   }

If you want, I can add tests mirroring the pnpm CI tests for the npm/lockfile cases.


196-204: Stream installer output to the user in long-running installs.

CombinedOutput() buffers everything; for large dependency trees the feedback comes late. Consider streaming to ctx.Writer (and keeping a trace buffer if you still want post-mortem logs).

-  install := exec.CommandContext(ctx.Context, cmd, args...)
+  install := exec.CommandContext(ctx.Context, cmd, args...)
+  // Stream logs for better UX during long installs
+  install.Stdout = ctx.Writer
+  install.Stderr = ctx.Writer

If you adopt this, adjust the error handling below to use install.Run() and omit the out-based messages (or capture to a bytes.Buffer via io.MultiWriter for trace logs).

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 5f4e77d and 739102c.

📒 Files selected for processing (2)
  • internal/bundler/bundler.go (1 hunks)
  • internal/bundler/bundler_test.go (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • internal/bundler/bundler_test.go
🧰 Additional context used
🧬 Code graph analysis (1)
internal/bundler/bundler.go (2)
internal/util/io.go (1)
  • Exists (14-19)
internal/project/project.go (2)
  • Project (145-154)
  • Bundler (481-490)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Analyze (go)

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

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 4d4d8d8 and 3598f35.

📒 Files selected for processing (1)
  • agentuity.schema.json (2 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Analyze (go)
  • GitHub Check: Build and Test (macos-latest)
🔇 Additional comments (1)
agentuity.schema.json (1)

224-224: No substantive change at EOF.

Closing brace touched; nothing to review here.

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: 0

♻️ Duplicate comments (1)
cmd/project.go (1)

64-67: Ensure yarn.lock triggers “yarn install” downstream (not npm) [duplicate of earlier concern].

Runtime remains “nodejs” for Yarn, so the bundler/dev paths must switch installers based on lockfiles.

#!/bin/bash
set -euo pipefail

# Look for installer branching in bundler/dev flows
rg -nP -C3 'exec\.Command\(\s*"(npm|yarn|pnpm)"' internal/bundler internal/dev || true
rg -nP -C2 'yarn\.lock|pnpm-lock\.yaml|package-lock\.json|bun\.lockb?' internal/bundler internal/dev || true

# Ideally we see:
# - yarn.lock -> yarn install --frozen-lockfile
# - pnpm-lock.yaml -> pnpm install --frozen-lockfile (CI-appropriate flags)
# - package-lock.json (or none) -> npm ci/install with safe flags
🧹 Nitpick comments (2)
cmd/project.go (2)

41-43: Name/doc mismatch: this returns a runtime, not a package manager (optional rename).

Consider renaming and tightening the comment to avoid confusion.

Apply:

-// detectPackageManager auto-detects the package manager based on lockfiles in the directory
-// If no lockfile is found, falls back to the default runtime from the template
-func detectPackageManager(dir string, defaultRuntime string) string {
+// resolveJSRuntimeFromLockfiles selects the JS runtime (nodejs|bunjs) by inspecting lockfiles in dir.
+// It does not encode the package manager (npm|yarn|pnpm); downstream code should choose installer accordingly.
+// If no relevant lockfile is found, it falls back to the template-provided runtime.
+func resolveJSRuntimeFromLockfiles(dir string, defaultRuntime string) string {

175-175: Update call site if you adopt the helper rename.

-    Runtime:    detectPackageManager(args.Dir, args.Provider.Runtime),
+    Runtime:    resolveJSRuntimeFromLockfiles(args.Dir, args.Provider.Runtime),
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 3598f35 and 950ca7d.

📒 Files selected for processing (1)
  • cmd/project.go (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
cmd/project.go (1)
internal/util/io.go (1)
  • Exists (14-19)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Analyze (go)
🔇 Additional comments (3)
cmd/project.go (3)

41-72: Lockfile precedence and JS-only guard look good.

bun > pnpm > npm > yarn ordering is sensible, and restricting detection to JS runtimes avoids accidental overrides.


170-180: Confirm and document all supported bundler runtimes

The code currently accepts more than just “nodejs” and “bunjs” for Bundler.Runtime. Please verify and update as needed:

  • Ensure the JSON-schema tags (if any) and any CLI or API documentation for project.Bundler.Runtime list the full set of valid values:
    • bunjs
    • nodejs
    • pnpm
    • deno
  • Confirm that detectPackageManager(args.Dir, args.Provider.Runtime) only ever returns one of those four and never “yarn” (or other unsupported strings).
  • If the schema or docs are out of date, please update the struct tags and documentation comments in:
    internal/project/project.go (runtime validation)
    • Any JSON-schema annotations in the project-definition package

44-48: JS Templates Must Declare “nodejs” or “bunjs” as Runtime

I wasn’t able to locate any template definition files in the repo to confirm their runtime fields. Since the code only auto-detects JavaScript runtimes when defaultRuntime is exactly "nodejs" or "bunjs", please manually verify that all JS-based templates include one of these values (and none are left empty or set to a different string).

• Verify that every template’s rules.yaml (or equivalent) under each <templateDir>/<identifier>/ defines runtime: "nodejs" or runtime: "bunjs".
• Ensure no JS template has an empty, missing, or non-(nodejs|bunjs) runtime value—otherwise detection will be skipped.
• If templates are sourced externally or embedded differently, confirm the runtime defaults are applied before loading.

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: 5

🧹 Nitpick comments (2)
internal/bundler/bundler.go (1)

170-210: Optional: strengthen CI installs for npm/yarn.

  • npm: prefer “ci” and enforce --omit=dev.
  • yarn: ensure --immutable on CI (Yarn 2+/Berry).

Apply this diff:

     if ctx.CI {
       if runtime == "bunjs" {
         ...
-      } else if cmd == "pnpm" {
+      } else if cmd == "pnpm" {
         ...
+      } else if cmd == "npm" {
+        if len(args) > 0 && args[0] == "install" {
+          args[0] = "ci"
+        }
+        seen := false
+        for _, a := range args {
+          if a == "--omit=dev" {
+            seen = true
+            break
+          }
+        }
+        if !seen {
+          args = append(args, "--omit=dev")
+        }
+      } else if cmd == "yarn" {
+        hasImmutable := false
+        for _, a := range args {
+          if a == "--immutable" || a == "--frozen-lockfile" {
+            hasImmutable = true
+            break
+          }
+        }
+        if !hasImmutable {
+          args = append(args, "--immutable")
+        }
       }
     }
internal/project/project.go (1)

196-202: Nit: clarify error messages to avoid “pnpm as runtime” confusion.

Consider hinting that the package manager is auto-detected from lockfiles; runtime is the JS engine.

Example:

- return fmt.Errorf("invalid bundler.runtime value: %s. only bunjs and nodejs are supported", p.Bundler.Runtime)
+ return fmt.Errorf("invalid bundler.runtime value: %s. only bunjs and nodejs are supported (runtime refers to the JS engine; package manager is auto-detected from lockfiles)", p.Bundler.Runtime)
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 950ca7d and 828b06f.

📒 Files selected for processing (2)
  • internal/bundler/bundler.go (2 hunks)
  • internal/project/project.go (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
internal/bundler/bundler.go (2)
internal/util/io.go (1)
  • Exists (14-19)
internal/project/project.go (1)
  • Bundler (493-502)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Analyze (go)
🔇 Additional comments (2)
internal/bundler/bundler.go (1)

212-238: LGTM: install execution flow and diagnostics.

Refactor to getJSInstallCommand + detailed exit-code reporting looks good.

internal/project/project.go (1)

196-202: LGTM: runtime validation aligned (JS: bunjs/nodejs; Python: uv).

Matches schema intent and keeps package manager separate from runtime.

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

♻️ Duplicate comments (2)
internal/bundler/bundler.go (2)

188-197: Do not mutate the slice while ranging; quiet flags may be skipped.

Removing items from args inside a range can skip elements. Build a filtered copy.

-    if runtime == "bunjs" {
-      // Replace silent flags with verbose for CI
-      for i, arg := range args {
-        if arg == "--no-progress" || arg == "--no-summary" || arg == "--silent" {
-          args = append(args[:i], args[i+1:]...)
-          i--
-        }
-      }
-      args = append(args, "--verbose", "--no-cache")
+    if runtime == "bunjs" {
+      // Drop quiet flags for CI using a filtered copy
+      filtered := make([]string, 0, len(args))
+      for _, arg := range args {
+        if arg == "--no-progress" || arg == "--no-summary" || arg == "--silent" {
+          continue
+        }
+        filtered = append(filtered, arg)
+      }
+      args = filtered
+      args = append(args, "--verbose", "--no-cache")

197-206: Fix PNPM CI flags gating and argument handling

The PNPM-specific CI flags block is still gated on runtime == "pnpm", but runtime only ever reflects the JavaScript engine (e.g. “nodejs” or “bunjs”), so this branch never executes. Additionally, removing --silent via in-place slicing can lead to index errors—use a filtered slice instead.

Locations to update:

  • internal/bundler/bundler.go, lines 197–206

Proposed fix:

--- a/internal/bundler/bundler.go
+++ b/internal/bundler/bundler.go
@@ -195,11 +195,16 @@ func buildArgs(runtime, cmd string, args []string) []string {
-   } else if runtime == "pnpm" {
+   } else if cmd == "pnpm" {
       // Remove silent flag and add CI-specific flags
-      for i, arg := range args {
-        if arg == "--silent" {
-          args = append(args[:i], args[i+1:]...)
-          break
-        }
-      }
-      args = append(args, "--reporter=append-only", "--frozen-lockfile")
+      filtered := make([]string, 0, len(args))
+      for _, arg := range args {
+        if arg == "--silent" {
+          continue
+        }
+        filtered = append(filtered, arg)
+      }
+      args = filtered
+      args = append(args, "--reporter=append-only", "--frozen-lockfile")
   }
   return args
 }
🧹 Nitpick comments (1)
internal/bundler/bundler.go (1)

151-153: Yarn: install only production deps in non-Dev builds.

Consider excluding devDeps to match npm/pnpm/bun behavior.

-    return "yarn", []string{"install", "--frozen-lockfile"}, nil
+    return "yarn", []string{"install", "--production=true", "--frozen-lockfile"}, nil
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 828b06f and 4844412.

📒 Files selected for processing (2)
  • cmd/project.go (2 hunks)
  • internal/bundler/bundler.go (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • cmd/project.go
🧰 Additional context used
🧬 Code graph analysis (1)
internal/bundler/bundler.go (2)
internal/util/io.go (1)
  • Exists (14-19)
internal/project/project.go (2)
  • Project (152-161)
  • Bundler (493-502)
🔇 Additional comments (1)
internal/bundler/bundler.go (1)

212-220: LGTM: centralizing install command generation improves maintainability.

The switch to getJSInstallCommand with unified logging and error handling is a good refactor.

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: 0

🧹 Nitpick comments (4)
internal/bundler/bundler.go (4)

144-155: Lockfile-based PM detection is correct; nice inclusion of bun.lockb.

Optional: consider detecting Yarn Berry (.yarnrc.yml with yarnPath/nodeLinker) to tailor flags accordingly.


157-175: Tiny UX win: make Yarn non-interactive by default.

Safer in scripts and CI even outside applyCIModifications.

 case "yarn":
-    return "yarn", []string{"install", "--frozen-lockfile"}, nil
+    return "yarn", []string{"install", "--frozen-lockfile", "--non-interactive"}, nil

188-219: Remove unused parameter ‘runtime’ from applyCIModifications.

It isn’t used and adds noise; simplify signature and call site.

-func applyCIModifications(ctx BundleContext, cmd, runtime string, args []string) []string {
+func applyCIModifications(ctx BundleContext, cmd string, args []string) []string {

And update the caller:

-args = applyCIModifications(ctx, cmd, runtime, args)
+args = applyCIModifications(ctx, cmd, args)

220-241: Prefer reproducible npm installs in CI: switch to ‘npm ci’ when package-lock.json exists.

This aligns with npm’s guidance and reduces flakiness.

 cmd, args, err := jsInstallCommandSpec(projectDir)
 if err != nil {
     return "", nil, err
 }
 
-// Apply CI-specific modifications
-args = applyCIModifications(ctx, cmd, runtime, args)
+// Prefer reproducible installs for npm in CI when a lockfile is present
+if ctx.CI && cmd == "npm" && util.Exists(filepath.Join(projectDir, "package-lock.json")) {
+    args = []string{"ci", "--omit=dev", "--ignore-scripts", "--no-audit", "--no-fund"}
+}
+
+// Apply CI-specific modifications
+args = applyCIModifications(ctx, cmd, args)
 
 return cmd, args, nil
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 4844412 and 660711a.

📒 Files selected for processing (5)
  • cmd/project.go (2 hunks)
  • internal/bundler/bundler.go (2 hunks)
  • internal/bundler/bundler_test.go (2 hunks)
  • internal/dev/dev.go (1 hunks)
  • internal/mcp/project.go (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • internal/dev/dev.go
🚧 Files skipped from review as they are similar to previous changes (3)
  • cmd/project.go
  • internal/bundler/bundler_test.go
  • internal/mcp/project.go
🧰 Additional context used
🧬 Code graph analysis (1)
internal/bundler/bundler.go (2)
internal/util/io.go (1)
  • Exists (14-19)
internal/project/project.go (2)
  • Project (152-161)
  • Bundler (493-502)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Build and Test (macos-latest)
  • GitHub Check: Analyze (go)
🔇 Additional comments (1)
internal/bundler/bundler.go (1)

244-268: Install integration via getJSInstallCommand looks good.

Good separation of concerns and error reporting on exit codes.

@coderabbitai coderabbitai bot requested review from jhaynie and pec1985 August 27, 2025 16:31
@coderabbitai coderabbitai bot requested a review from robindiddams August 27, 2025 16:31
Copy link
Member

@potofpie potofpie left a comment

Choose a reason for hiding this comment

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

LGTM

@potofpie potofpie dismissed jhaynie’s stale review August 27, 2025 16:32

I went through your comment with Gabe and it looks good now

@Huijiro Huijiro merged commit ba92eac into main Aug 27, 2025
13 of 14 checks passed
@Huijiro Huijiro deleted the grodrigues/agent-545-add-pnpm-support branch August 27, 2025 16:49
devin-ai-integration bot added a commit that referenced this pull request Aug 28, 2025
- Added: Adds support for pnpm as a bundler (#438)
- Fixed: bump go-common to get light mode color fixes

Co-Authored-By: unknown <>
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.

4 participants