From 4855da4f66830cf82da43899178e070d59d70c99 Mon Sep 17 00:00:00 2001 From: huijiro Date: Mon, 25 Aug 2025 13:50:42 -0300 Subject: [PATCH 01/10] Adds support for pnpm as a bundler --- agentuity.schema.json | 1 + cmd/project.go | 34 +++++++++++++++++++++++++++++++++- internal/bundler/bundler.go | 8 ++++++++ internal/dev/dev.go | 4 ++-- internal/mcp/project.go | 2 +- internal/project/project.go | 4 ++-- 6 files changed, 47 insertions(+), 6 deletions(-) diff --git a/agentuity.schema.json b/agentuity.schema.json index d1a7f371..a7c74324 100644 --- a/agentuity.schema.json +++ b/agentuity.schema.json @@ -125,6 +125,7 @@ }, "runtime": { "type": "string", + "enum": ["nodejs", "bunjs", "pnpm"], "description": "The runtime environment" }, "agents": { diff --git a/cmd/project.go b/cmd/project.go index 79bafe63..bcf60ec9 100644 --- a/cmd/project.go +++ b/cmd/project.go @@ -38,6 +38,38 @@ Use the subcommands to manage your projects.`, }, } +// 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 { + // Only auto-detect for JavaScript-based runtimes + if defaultRuntime != "nodejs" && defaultRuntime != "bunjs" && defaultRuntime != "pnpm" { + return defaultRuntime + } + + // 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")) { + 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 +} + func saveEnv(dir string, apikey string, projectKey string) { filename := filepath.Join(dir, ".env") envLines, err := env.ParseEnvFile(filename) @@ -140,7 +172,7 @@ func initProject(ctx context.Context, logger logger.Logger, args InitProjectArgs Identifier: args.Provider.Identifier, Language: args.Provider.Language, Framework: args.Provider.Framework, - Runtime: args.Provider.Runtime, + Runtime: detectPackageManager(args.Dir, args.Provider.Runtime), Ignore: args.Provider.Bundle.Ignore, AgentConfig: project.AgentBundlerConfig{ Dir: args.Provider.SrcDir, diff --git a/internal/bundler/bundler.go b/internal/bundler/bundler.go index 15c06423..d33490b8 100644 --- a/internal/bundler/bundler.go +++ b/internal/bundler/bundler.go @@ -156,6 +156,14 @@ func bundleJavascript(ctx BundleContext, dir string, outdir string, theproject * 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=default") + } else { + args = append(args, "--reporter=silent") + } + install = exec.CommandContext(ctx.Context, "pnpm", args...) default: return fmt.Errorf("unsupported runtime: %s", theproject.Bundler.Runtime) } diff --git a/internal/dev/dev.go b/internal/dev/dev.go index 335c1f2e..89df6487 100644 --- a/internal/dev/dev.go +++ b/internal/dev/dev.go @@ -125,9 +125,9 @@ func CreateRunProjectCmd(ctx context.Context, log logger.Logger, theproject proj projectServerCmd.Env = append(projectServerCmd.Env, "NODE_ENV=development") } - // for nodejs, we need to enable source maps directly in the environment. + // for nodejs and pnpm, we need to enable source maps directly in the environment. // for bun, we need to inject a shim helper to parse the source maps - if theproject.Project.Bundler.Runtime == "nodejs" { + if theproject.Project.Bundler.Runtime == "nodejs" || theproject.Project.Bundler.Runtime == "pnpm" { nodeOptions := os.Getenv("NODE_OPTIONS") if nodeOptions == "" { nodeOptions = "--enable-source-maps" diff --git a/internal/mcp/project.go b/internal/mcp/project.go index 93e71724..d249e8f9 100644 --- a/internal/mcp/project.go +++ b/internal/mcp/project.go @@ -23,7 +23,7 @@ type CreateProjectArguments struct { } type ListTemplatesArguments struct { - Provider string `json:"provider" jsonschema:"required,description=The provider to use for the project which can be either 'bunjs' for BunJS, 'nodejs' for NodeJS or 'python-uv' for Python with UV"` + Provider string `json:"provider" jsonschema:"required,description=The provider to use for the project which can be either 'bunjs' for BunJS, 'nodejs' for NodeJS, 'pnpm' for PNPM, or 'python-uv' for Python with UV"` } func init() { diff --git a/internal/project/project.go b/internal/project/project.go index 35a6597a..1cd220be 100644 --- a/internal/project/project.go +++ b/internal/project/project.go @@ -186,8 +186,8 @@ func (p *Project) Load(dir string) error { } switch p.Bundler.Language { case "js", "javascript", "typescript": - if p.Bundler.Runtime != "bunjs" && p.Bundler.Runtime != "nodejs" && p.Bundler.Runtime != "deno" { - return fmt.Errorf("invalid bundler.runtime value: %s. only bunjs, nodejs, and deno are supported", p.Bundler.Runtime) + if p.Bundler.Runtime != "bunjs" && p.Bundler.Runtime != "nodejs" && p.Bundler.Runtime != "deno" && p.Bundler.Runtime != "pnpm" { + return fmt.Errorf("invalid bundler.runtime value: %s. only bunjs, nodejs, pnpm, and deno are supported", p.Bundler.Runtime) } case "py", "python": if p.Bundler.Runtime != "uv" && p.Bundler.Runtime != "python" && p.Bundler.Runtime != "" { From 5f4e77d50f012155619a9716b31658ff8658df6e Mon Sep 17 00:00:00 2001 From: huijiro Date: Mon, 25 Aug 2025 14:45:49 -0300 Subject: [PATCH 02/10] Updated with feedback --- agentuity.schema.json | 2 +- internal/bundler/bundler.go | 14 ++-- internal/bundler/bundler_test.go | 122 +++++++++++++++++++++++++++++++ internal/project/project.go | 4 +- 4 files changed, 132 insertions(+), 10 deletions(-) diff --git a/agentuity.schema.json b/agentuity.schema.json index a7c74324..2342116f 100644 --- a/agentuity.schema.json +++ b/agentuity.schema.json @@ -125,7 +125,7 @@ }, "runtime": { "type": "string", - "enum": ["nodejs", "bunjs", "pnpm"], + "enum": ["nodejs", "bunjs", "pnpm", "deno", "uv"], "description": "The runtime environment" }, "agents": { diff --git a/internal/bundler/bundler.go b/internal/bundler/bundler.go index d33490b8..ff664546 100644 --- a/internal/bundler/bundler.go +++ b/internal/bundler/bundler.go @@ -147,7 +147,11 @@ func bundleJavascript(ctx BundleContext, dir string, outdir string, theproject * var install *exec.Cmd switch theproject.Bundler.Runtime { case "nodejs": - install = exec.CommandContext(ctx.Context, "npm", "install", "--no-save", "--no-audit", "--no-fund", "--include=prod", "--ignore-scripts") + 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 { @@ -159,9 +163,9 @@ func bundleJavascript(ctx BundleContext, dir string, outdir string, theproject * case "pnpm": args := []string{"install", "--prod", "--ignore-scripts"} if ctx.CI { - args = append(args, "--reporter=default") + args = append(args, "--reporter=append-only", "--frozen-lockfile") } else { - args = append(args, "--reporter=silent") + args = append(args, "--silent") } install = exec.CommandContext(ctx.Context, "pnpm", args...) default: @@ -333,10 +337,6 @@ func bundlePython(ctx BundleContext, dir string, outdir string, theproject *proj switch theproject.Bundler.Runtime { case "uv": install = exec.CommandContext(ctx.Context, "uv", "sync", "--no-dev", "--frozen", "--quiet", "--no-progress") - case "pip": - install = exec.CommandContext(ctx.Context, "uv", "pip", "install", "--quiet", "--no-progress") - case "poetry": - return fmt.Errorf("poetry is not supported yet") default: return fmt.Errorf("unsupported runtime: %s", theproject.Bundler.Runtime) } diff --git a/internal/bundler/bundler_test.go b/internal/bundler/bundler_test.go index ae9c4f13..bb084af0 100644 --- a/internal/bundler/bundler_test.go +++ b/internal/bundler/bundler_test.go @@ -1,9 +1,12 @@ package bundler import ( + "os" + "path/filepath" "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestPyProject(t *testing.T) { @@ -32,3 +35,122 @@ func TestPyProject(t *testing.T) { assert.Equal(t, "1.0.0-alpha", pyProjectVersionRegex.FindStringSubmatch(`version = "1.0.0-alpha"`)[1]) assert.Equal(t, "1.0.0-beta", pyProjectVersionRegex.FindStringSubmatch(`version = "1.0.0-beta"`)[1]) } + +// Helper function to test package manager detection logic +func testPackageManagerCommand(t *testing.T, tempDir string, runtime string, expectedCmd string, expectedArgs []string) { + // Test the command creation logic directly by examining what would be created + switch runtime { + case "nodejs": + if _, err := os.Stat(filepath.Join(tempDir, "yarn.lock")); err == nil { + // yarn.lock exists + assert.Equal(t, "yarn", expectedCmd) + assert.Equal(t, []string{"install", "--frozen-lockfile"}, expectedArgs) + } else { + // no yarn.lock + assert.Equal(t, "npm", expectedCmd) + assert.Equal(t, []string{"install", "--no-audit", "--no-fund", "--include=prod", "--ignore-scripts"}, expectedArgs) + } + case "pnpm": + assert.Equal(t, "pnpm", expectedCmd) + assert.Equal(t, []string{"install", "--prod", "--ignore-scripts", "--silent"}, expectedArgs) + case "bunjs": + assert.Equal(t, "bun", expectedCmd) + assert.Equal(t, []string{"install", "--production", "--no-save", "--ignore-scripts", "--no-progress", "--no-summary", "--silent"}, expectedArgs) + } +} + +func TestJavaScriptPackageManagerDetection(t *testing.T) { + tests := []struct { + name string + runtime string + lockFiles []string + expectedCmd string + expectedArgs []string + }{ + { + name: "nodejs with yarn.lock should use yarn", + runtime: "nodejs", + lockFiles: []string{"yarn.lock"}, + expectedCmd: "yarn", + expectedArgs: []string{"install", "--frozen-lockfile"}, + }, + { + name: "nodejs without yarn.lock should use npm", + runtime: "nodejs", + lockFiles: []string{}, + expectedCmd: "npm", + expectedArgs: []string{"install", "--no-audit", "--no-fund", "--include=prod", "--ignore-scripts"}, + }, + { + name: "nodejs with package-lock.json should use npm", + runtime: "nodejs", + lockFiles: []string{"package-lock.json"}, + expectedCmd: "npm", + expectedArgs: []string{"install", "--no-audit", "--no-fund", "--include=prod", "--ignore-scripts"}, + }, + { + name: "nodejs with both yarn.lock and package-lock.json should prefer yarn", + runtime: "nodejs", + lockFiles: []string{"yarn.lock", "package-lock.json"}, + expectedCmd: "yarn", + expectedArgs: []string{"install", "--frozen-lockfile"}, + }, + { + name: "pnpm runtime should use pnpm", + runtime: "pnpm", + lockFiles: []string{"pnpm-lock.yaml"}, + expectedCmd: "pnpm", + expectedArgs: []string{"install", "--prod", "--ignore-scripts", "--silent"}, + }, + { + name: "bunjs runtime should use bun", + runtime: "bunjs", + lockFiles: []string{"bun.lockb"}, + expectedCmd: "bun", + expectedArgs: []string{"install", "--production", "--no-save", "--ignore-scripts", "--no-progress", "--no-summary", "--silent"}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Create temporary directory + tempDir := t.TempDir() + + // Create lock files + for _, lockFile := range tt.lockFiles { + filePath := filepath.Join(tempDir, lockFile) + err := os.WriteFile(filePath, []byte(""), 0644) + require.NoError(t, err) + } + + // Test the logic + testPackageManagerCommand(t, tempDir, tt.runtime, tt.expectedCmd, tt.expectedArgs) + }) + } +} + +func TestPnpmCIFlags(t *testing.T) { + // Test that pnpm in CI mode uses correct flags + tempDir := t.TempDir() + + // Create pnpm-lock.yaml + lockFile := filepath.Join(tempDir, "pnpm-lock.yaml") + err := os.WriteFile(lockFile, []byte(""), 0644) + require.NoError(t, err) + + // Test CI mode + expectedCmdCI := "pnpm" + expectedArgsCI := []string{"install", "--prod", "--ignore-scripts", "--reporter=append-only", "--frozen-lockfile"} + + // Test non-CI mode + expectedCmdNonCI := "pnpm" + expectedArgsNonCI := []string{"install", "--prod", "--ignore-scripts", "--silent"} + + // Test that CI flags are correct + assert.Equal(t, "pnpm", expectedCmdCI) + assert.Equal(t, []string{"install", "--prod", "--ignore-scripts", "--reporter=append-only", "--frozen-lockfile"}, expectedArgsCI) + + // Test that non-CI flags are correct + assert.Equal(t, "pnpm", expectedCmdNonCI) + assert.Equal(t, []string{"install", "--prod", "--ignore-scripts", "--silent"}, expectedArgsNonCI) +} diff --git a/internal/project/project.go b/internal/project/project.go index 1cd220be..ff35928d 100644 --- a/internal/project/project.go +++ b/internal/project/project.go @@ -190,8 +190,8 @@ func (p *Project) Load(dir string) error { return fmt.Errorf("invalid bundler.runtime value: %s. only bunjs, nodejs, pnpm, and deno are supported", p.Bundler.Runtime) } case "py", "python": - if p.Bundler.Runtime != "uv" && p.Bundler.Runtime != "python" && p.Bundler.Runtime != "" { - return fmt.Errorf("invalid bundler.runtime value: %s. only uv or python is supported", p.Bundler.Runtime) + if p.Bundler.Runtime != "uv" { + return fmt.Errorf("invalid bundler.runtime value: %s. only uv is supported", p.Bundler.Runtime) } default: return fmt.Errorf("invalid bundler.language value: %s. only js or py are supported", p.Bundler.Language) From 739102ceca62c07284dd552b95968c7dce90ba90 Mon Sep 17 00:00:00 2001 From: huijiro Date: Mon, 25 Aug 2025 15:29:37 -0300 Subject: [PATCH 03/10] Fixed feedback --- internal/bundler/bundler.go | 83 ++++++++++++++++++++++---------- internal/bundler/bundler_test.go | 75 ++++++++++++----------------- 2 files changed, 89 insertions(+), 69 deletions(-) diff --git a/internal/bundler/bundler.go b/internal/bundler/bundler.go index ff664546..8f9ab8ea 100644 --- a/internal/bundler/bundler.go +++ b/internal/bundler/bundler.go @@ -141,36 +141,67 @@ func runTypecheck(ctx BundleContext, dir string) error { return nil } -func bundleJavascript(ctx BundleContext, dir string, outdir string, theproject *project.Project) error { +// jsInstallCommandSpec returns the base command name and arguments for installing JavaScript dependencies +// This function returns the base command without CI-specific modifications +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 + } + 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 ctx.Install || !util.Exists(filepath.Join(dir, "node_modules")) { - 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") +// getJSInstallCommand returns the complete install command with CI modifications applied +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" { + // 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-- + } } - 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") + 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 + } } - install = exec.CommandContext(ctx.Context, "pnpm", args...) - default: - return fmt.Errorf("unsupported runtime: %s", theproject.Bundler.Runtime) + args = append(args, "--reporter=append-only", "--frozen-lockfile") } + } + + return cmd, args, nil +} + +func bundleJavascript(ctx BundleContext, dir string, outdir string, theproject *project.Project) error { + + if ctx.Install || !util.Exists(filepath.Join(dir, "node_modules")) { + cmd, args, err := getJSInstallCommand(ctx, dir, theproject.Bundler.Runtime) + if err != nil { + return err + } + + install := exec.CommandContext(ctx.Context, cmd, args...) util.ProcessSetup(install) install.Dir = dir out, err := install.CombinedOutput() diff --git a/internal/bundler/bundler_test.go b/internal/bundler/bundler_test.go index bb084af0..eb82ed85 100644 --- a/internal/bundler/bundler_test.go +++ b/internal/bundler/bundler_test.go @@ -1,6 +1,7 @@ package bundler import ( + "context" "os" "path/filepath" "testing" @@ -37,26 +38,17 @@ func TestPyProject(t *testing.T) { } // Helper function to test package manager detection logic -func testPackageManagerCommand(t *testing.T, tempDir string, runtime string, expectedCmd string, expectedArgs []string) { - // Test the command creation logic directly by examining what would be created - switch runtime { - case "nodejs": - if _, err := os.Stat(filepath.Join(tempDir, "yarn.lock")); err == nil { - // yarn.lock exists - assert.Equal(t, "yarn", expectedCmd) - assert.Equal(t, []string{"install", "--frozen-lockfile"}, expectedArgs) - } else { - // no yarn.lock - assert.Equal(t, "npm", expectedCmd) - assert.Equal(t, []string{"install", "--no-audit", "--no-fund", "--include=prod", "--ignore-scripts"}, expectedArgs) - } - case "pnpm": - assert.Equal(t, "pnpm", expectedCmd) - assert.Equal(t, []string{"install", "--prod", "--ignore-scripts", "--silent"}, expectedArgs) - case "bunjs": - assert.Equal(t, "bun", expectedCmd) - assert.Equal(t, []string{"install", "--production", "--no-save", "--ignore-scripts", "--no-progress", "--no-summary", "--silent"}, expectedArgs) +func testPackageManagerCommand(t *testing.T, tempDir string, runtime string, isCI bool, expectedCmd string, expectedArgs []string) { + ctx := BundleContext{ + Context: context.Background(), + CI: isCI, } + + actualCmd, actualArgs, err := getJSInstallCommand(ctx, tempDir, runtime) + require.NoError(t, err) + + assert.Equal(t, expectedCmd, actualCmd) + assert.Equal(t, expectedArgs, actualArgs) } func TestJavaScriptPackageManagerDetection(t *testing.T) { @@ -123,34 +115,31 @@ func TestJavaScriptPackageManagerDetection(t *testing.T) { require.NoError(t, err) } - // Test the logic - testPackageManagerCommand(t, tempDir, tt.runtime, tt.expectedCmd, tt.expectedArgs) + // Test the logic with CI=false + testPackageManagerCommand(t, tempDir, tt.runtime, false, tt.expectedCmd, tt.expectedArgs) }) } } func TestPnpmCIFlags(t *testing.T) { - // Test that pnpm in CI mode uses correct flags tempDir := t.TempDir() - - // Create pnpm-lock.yaml - lockFile := filepath.Join(tempDir, "pnpm-lock.yaml") - err := os.WriteFile(lockFile, []byte(""), 0644) - require.NoError(t, err) - - // Test CI mode - expectedCmdCI := "pnpm" - expectedArgsCI := []string{"install", "--prod", "--ignore-scripts", "--reporter=append-only", "--frozen-lockfile"} - - // Test non-CI mode - expectedCmdNonCI := "pnpm" - expectedArgsNonCI := []string{"install", "--prod", "--ignore-scripts", "--silent"} - - // Test that CI flags are correct - assert.Equal(t, "pnpm", expectedCmdCI) - assert.Equal(t, []string{"install", "--prod", "--ignore-scripts", "--reporter=append-only", "--frozen-lockfile"}, expectedArgsCI) - - // Test that non-CI flags are correct - assert.Equal(t, "pnpm", expectedCmdNonCI) - assert.Equal(t, []string{"install", "--prod", "--ignore-scripts", "--silent"}, expectedArgsNonCI) + require.NoError(t, os.WriteFile(filepath.Join(tempDir, "pnpm-lock.yaml"), []byte(""), 0644)) + // CI=true + testPackageManagerCommand( + t, + tempDir, + "pnpm", + true, + "pnpm", + []string{"install", "--prod", "--ignore-scripts", "--reporter=append-only", "--frozen-lockfile"}, + ) + // CI=false + testPackageManagerCommand( + t, + tempDir, + "pnpm", + false, + "pnpm", + []string{"install", "--prod", "--ignore-scripts", "--silent"}, + ) } From 3598f352041a17dd1721a43b7fc6dc4399c718f5 Mon Sep 17 00:00:00 2001 From: huijiro Date: Tue, 26 Aug 2025 19:59:54 -0300 Subject: [PATCH 04/10] Removed pnpm and deno from spec --- agentuity.schema.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/agentuity.schema.json b/agentuity.schema.json index b7ee5bd3..f8452cee 100644 --- a/agentuity.schema.json +++ b/agentuity.schema.json @@ -170,7 +170,7 @@ }, "runtime": { "type": "string", - "enum": ["nodejs", "bunjs", "pnpm", "deno", "uv"], + "enum": ["nodejs", "bunjs", "uv"], "description": "The runtime environment" }, "agents": { @@ -221,4 +221,4 @@ "description": "The agents that are part of this project" } } -} \ No newline at end of file +} From 950ca7d874fcfab48c914526723eeb3aa0ab54bf Mon Sep 17 00:00:00 2001 From: huijiro Date: Wed, 27 Aug 2025 12:13:30 -0300 Subject: [PATCH 05/10] Changed runtime options --- cmd/project.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/cmd/project.go b/cmd/project.go index e75693cd..089366c5 100644 --- a/cmd/project.go +++ b/cmd/project.go @@ -42,20 +42,20 @@ Use the subcommands to manage your projects.`, // If no lockfile is found, falls back to the default runtime from the template func detectPackageManager(dir string, defaultRuntime string) string { // Only auto-detect for JavaScript-based runtimes - if defaultRuntime != "nodejs" && defaultRuntime != "bunjs" && defaultRuntime != "pnpm" { + if defaultRuntime != "nodejs" && defaultRuntime != "bunjs" { return defaultRuntime } - // 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 pnpm lockfile first + if util.Exists(filepath.Join(dir, "pnpm-lock.yaml")) { + return "nodejs" + } + // Check for npm lockfile if util.Exists(filepath.Join(dir, "package-lock.json")) { return "nodejs" From 828b06fd55e5553e991a15f7a16ffb6a66b3d521 Mon Sep 17 00:00:00 2001 From: huijiro Date: Wed, 27 Aug 2025 12:46:12 -0300 Subject: [PATCH 06/10] Fix the jsInstallCommandSpec --- internal/bundler/bundler.go | 23 +++++++++-------------- internal/project/project.go | 4 ++-- 2 files changed, 11 insertions(+), 16 deletions(-) diff --git a/internal/bundler/bundler.go b/internal/bundler/bundler.go index 721008ea..e2664411 100644 --- a/internal/bundler/bundler.go +++ b/internal/bundler/bundler.go @@ -143,20 +143,15 @@ func runTypecheck(ctx BundleContext, dir string) error { // jsInstallCommandSpec returns the base command name and arguments for installing JavaScript dependencies // This function returns the base command without CI-specific modifications -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 - } - case "bunjs": - return "bun", []string{"install", "--production", "--ignore-scripts", "--no-progress", "--no-summary", "--silent"}, nil - case "pnpm": +func jsInstallCommandSpec(projectDir string) (string, []string, error) { + if util.Exists(filepath.Join(projectDir, "pnpm-lock.yaml")) { return "pnpm", []string{"install", "--prod", "--ignore-scripts", "--silent"}, nil - default: - return "", nil, fmt.Errorf("unsupported runtime: %s", runtime) + } else if util.Exists(filepath.Join(projectDir, "bun.lock")) { + return "bun", []string{"install", "--production", "--ignore-scripts", "--no-progress", "--no-summary", "--silent"}, nil + } else 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 } } @@ -183,7 +178,7 @@ func getJSInstallCommand(ctx BundleContext, projectDir, runtime string) (string, } } - cmd, args, err := jsInstallCommandSpec(ctx.Context, projectDir, runtime) + cmd, args, err := jsInstallCommandSpec(projectDir) if err != nil { return "", nil, err } diff --git a/internal/project/project.go b/internal/project/project.go index 434b3cd7..37740d49 100644 --- a/internal/project/project.go +++ b/internal/project/project.go @@ -193,8 +193,8 @@ func (p *Project) Load(dir string) error { } switch p.Bundler.Language { case "js", "javascript", "typescript": - if p.Bundler.Runtime != "bunjs" && p.Bundler.Runtime != "nodejs" && p.Bundler.Runtime != "deno" && p.Bundler.Runtime != "pnpm" { - return fmt.Errorf("invalid bundler.runtime value: %s. only bunjs, nodejs, pnpm, and deno are supported", p.Bundler.Runtime) + if p.Bundler.Runtime != "bunjs" && p.Bundler.Runtime != "nodejs" { + return fmt.Errorf("invalid bundler.runtime value: %s. only bunjs and nodejs are supported", p.Bundler.Runtime) } case "py", "python": if p.Bundler.Runtime != "uv" { From 48444126b0872718b10031dd3035a953b43e82d3 Mon Sep 17 00:00:00 2001 From: huijiro Date: Wed, 27 Aug 2025 12:54:45 -0300 Subject: [PATCH 07/10] Fixed test and checks for bun --- cmd/project.go | 4 ++-- internal/bundler/bundler.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cmd/project.go b/cmd/project.go index 089366c5..41d3e27e 100644 --- a/cmd/project.go +++ b/cmd/project.go @@ -46,8 +46,8 @@ func detectPackageManager(dir string, defaultRuntime string) string { return defaultRuntime } - // Check for bun lockfiles - if util.Exists(filepath.Join(dir, "bun.lockb")) || util.Exists(filepath.Join(dir, "bun.lock")) { + // Check for bun lockfile + if util.Exists(filepath.Join(dir, "bun.lockb")) { return "bunjs" } diff --git a/internal/bundler/bundler.go b/internal/bundler/bundler.go index e2664411..ba02a54c 100644 --- a/internal/bundler/bundler.go +++ b/internal/bundler/bundler.go @@ -146,7 +146,7 @@ func runTypecheck(ctx BundleContext, dir string) error { func jsInstallCommandSpec(projectDir string) (string, []string, error) { if util.Exists(filepath.Join(projectDir, "pnpm-lock.yaml")) { return "pnpm", []string{"install", "--prod", "--ignore-scripts", "--silent"}, nil - } else if util.Exists(filepath.Join(projectDir, "bun.lock")) { + } else if util.Exists(filepath.Join(projectDir, "bun.lockb")) { return "bun", []string{"install", "--production", "--ignore-scripts", "--no-progress", "--no-summary", "--silent"}, nil } else if util.Exists(filepath.Join(projectDir, "yarn.lock")) { return "yarn", []string{"install", "--frozen-lockfile"}, nil From 660711a4728c27cedc4ee67136c89a80abc8ee2f Mon Sep 17 00:00:00 2001 From: huijiro Date: Wed, 27 Aug 2025 13:24:56 -0300 Subject: [PATCH 08/10] Fixed feedback and separated concerns of runtime and bundling properly --- cmd/project.go | 27 ++++------- internal/bundler/bundler.go | 82 ++++++++++++++++++++++---------- internal/bundler/bundler_test.go | 13 +++-- internal/dev/dev.go | 2 +- internal/mcp/project.go | 2 +- 5 files changed, 78 insertions(+), 48 deletions(-) diff --git a/cmd/project.go b/cmd/project.go index 41d3e27e..995c6819 100644 --- a/cmd/project.go +++ b/cmd/project.go @@ -38,31 +38,24 @@ Use the subcommands to manage your projects.`, }, } -// detectPackageManager auto-detects the package manager based on lockfiles in the directory +// detectRuntime auto-detects the runtime 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 { +func detectRuntime(dir string, defaultRuntime string) string { // Only auto-detect for JavaScript-based runtimes if defaultRuntime != "nodejs" && defaultRuntime != "bunjs" { return defaultRuntime } - // Check for bun lockfile - if util.Exists(filepath.Join(dir, "bun.lockb")) { + // Check for bun lockfiles - use bunjs runtime + if util.Exists(filepath.Join(dir, "bun.lockb")) || + util.Exists(filepath.Join(dir, "bun.lock")) { return "bunjs" } - // Check for pnpm lockfile first - if util.Exists(filepath.Join(dir, "pnpm-lock.yaml")) { - return "nodejs" - } - - // Check for npm lockfile - if util.Exists(filepath.Join(dir, "package-lock.json")) { - return "nodejs" - } - - // Check for yarn lockfile (fallback to nodejs for yarn projects) - if util.Exists(filepath.Join(dir, "yarn.lock")) { + // Check for nodejs runtime + if util.Exists(filepath.Join(dir, "pnpm-lock.yaml")) || + util.Exists(filepath.Join(dir, "package-lock.json")) || + util.Exists(filepath.Join(dir, "yarn.lock")) { return "nodejs" } @@ -172,7 +165,7 @@ func initProject(ctx context.Context, logger logger.Logger, args InitProjectArgs Identifier: args.Provider.Identifier, Language: args.Provider.Language, Framework: args.Provider.Framework, - Runtime: detectPackageManager(args.Dir, args.Provider.Runtime), + Runtime: detectRuntime(args.Dir, args.Provider.Runtime), Ignore: args.Provider.Bundle.Ignore, AgentConfig: project.AgentBundlerConfig{ Dir: args.Provider.SrcDir, diff --git a/internal/bundler/bundler.go b/internal/bundler/bundler.go index ba02a54c..570579d2 100644 --- a/internal/bundler/bundler.go +++ b/internal/bundler/bundler.go @@ -141,17 +141,35 @@ func runTypecheck(ctx BundleContext, dir string) error { return nil } +// detectPackageManager detects which package manager to use based on lockfiles +func detectPackageManager(projectDir string) string { + if util.Exists(filepath.Join(projectDir, "pnpm-lock.yaml")) { + return "pnpm" + } else if util.Exists(filepath.Join(projectDir, "bun.lockb")) || util.Exists(filepath.Join(projectDir, "bun.lock")) { + return "bun" + } else if util.Exists(filepath.Join(projectDir, "yarn.lock")) { + return "yarn" + } else { + return "npm" + } +} + // jsInstallCommandSpec returns the base command name and arguments for installing JavaScript dependencies // This function returns the base command without CI-specific modifications func jsInstallCommandSpec(projectDir string) (string, []string, error) { - if util.Exists(filepath.Join(projectDir, "pnpm-lock.yaml")) { + packageManager := detectPackageManager(projectDir) + + switch packageManager { + case "pnpm": return "pnpm", []string{"install", "--prod", "--ignore-scripts", "--silent"}, nil - } else if util.Exists(filepath.Join(projectDir, "bun.lockb")) { + case "bun": return "bun", []string{"install", "--production", "--ignore-scripts", "--no-progress", "--no-summary", "--silent"}, nil - } else if util.Exists(filepath.Join(projectDir, "yarn.lock")) { + case "yarn": return "yarn", []string{"install", "--frozen-lockfile"}, nil - } else { - return "npm", []string{"install", "--no-audit", "--no-fund", "--include=prod", "--ignore-scripts"}, nil + case "npm": + return "npm", []string{"install", "--no-audit", "--no-fund", "--omit=dev", "--ignore-scripts"}, nil + default: + return "npm", []string{"install", "--no-audit", "--no-fund", "--omit=dev", "--ignore-scripts"}, nil } } @@ -167,6 +185,38 @@ func generateBunLockfile(ctx BundleContext, logger logger.Logger, dir string) er return nil } +// applyCIModifications applies CI-specific modifications to install command arguments +func applyCIModifications(ctx BundleContext, cmd, runtime string, args []string) []string { + if !ctx.CI { + return args + } + + if cmd == "bun" { + // 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) + } + return filtered + } else if cmd == "pnpm" { + // Remove silent flag and add CI-specific flags + filtered := make([]string, 0, len(args)+2) + for _, arg := range args { + if arg == "--silent" { + continue + } + filtered = append(filtered, arg) + } + filtered = append(filtered, "--reporter=append-only", "--frozen-lockfile") + return filtered + } + + return args +} + // getJSInstallCommand returns the complete install command with CI modifications applied func getJSInstallCommand(ctx BundleContext, projectDir, runtime string) (string, []string, error) { // For bun, we need to ensure the lockfile is up to date before we can run the install @@ -184,27 +234,7 @@ func getJSInstallCommand(ctx BundleContext, projectDir, runtime string) (string, } // Apply CI-specific modifications - 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-- - } - } - 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 - } - } - args = append(args, "--reporter=append-only", "--frozen-lockfile") - } - } + args = applyCIModifications(ctx, cmd, runtime, args) return cmd, args, nil } diff --git a/internal/bundler/bundler_test.go b/internal/bundler/bundler_test.go index 6da64d84..b2e0a546 100644 --- a/internal/bundler/bundler_test.go +++ b/internal/bundler/bundler_test.go @@ -72,14 +72,14 @@ func TestJavaScriptPackageManagerDetection(t *testing.T) { runtime: "nodejs", lockFiles: []string{}, expectedCmd: "npm", - expectedArgs: []string{"install", "--no-audit", "--no-fund", "--include=prod", "--ignore-scripts"}, + expectedArgs: []string{"install", "--no-audit", "--no-fund", "--omit=dev", "--ignore-scripts"}, }, { name: "nodejs with package-lock.json should use npm", runtime: "nodejs", lockFiles: []string{"package-lock.json"}, expectedCmd: "npm", - expectedArgs: []string{"install", "--no-audit", "--no-fund", "--include=prod", "--ignore-scripts"}, + expectedArgs: []string{"install", "--no-audit", "--no-fund", "--omit=dev", "--ignore-scripts"}, }, { name: "nodejs with both yarn.lock and package-lock.json should prefer yarn", @@ -96,12 +96,19 @@ func TestJavaScriptPackageManagerDetection(t *testing.T) { expectedArgs: []string{"install", "--prod", "--ignore-scripts", "--silent"}, }, { - name: "bunjs runtime should use bun", + name: "bunjs runtime should use bun with bun.lockb", runtime: "bunjs", lockFiles: []string{"bun.lockb", "package.json"}, expectedCmd: "bun", expectedArgs: []string{"install", "--production", "--ignore-scripts", "--no-progress", "--no-summary", "--silent"}, }, + { + name: "bunjs runtime should use bun with bun.lock", + runtime: "bunjs", + lockFiles: []string{"bun.lock", "package.json"}, + expectedCmd: "bun", + expectedArgs: []string{"install", "--production", "--ignore-scripts", "--no-progress", "--no-summary", "--silent"}, + }, } for _, tt := range tests { diff --git a/internal/dev/dev.go b/internal/dev/dev.go index 89df6487..d7d999f0 100644 --- a/internal/dev/dev.go +++ b/internal/dev/dev.go @@ -127,7 +127,7 @@ func CreateRunProjectCmd(ctx context.Context, log logger.Logger, theproject proj // for nodejs and pnpm, we need to enable source maps directly in the environment. // for bun, we need to inject a shim helper to parse the source maps - if theproject.Project.Bundler.Runtime == "nodejs" || theproject.Project.Bundler.Runtime == "pnpm" { + if theproject.Project.Bundler.Runtime == "nodejs" { nodeOptions := os.Getenv("NODE_OPTIONS") if nodeOptions == "" { nodeOptions = "--enable-source-maps" diff --git a/internal/mcp/project.go b/internal/mcp/project.go index d249e8f9..2a9459aa 100644 --- a/internal/mcp/project.go +++ b/internal/mcp/project.go @@ -23,7 +23,7 @@ type CreateProjectArguments struct { } type ListTemplatesArguments struct { - Provider string `json:"provider" jsonschema:"required,description=The provider to use for the project which can be either 'bunjs' for BunJS, 'nodejs' for NodeJS, 'pnpm' for PNPM, or 'python-uv' for Python with UV"` + Provider string `json:"provider" jsonschema:"required,description=The provider to use for the project which can be either 'bunjs' for BunJS, 'nodejs' for NodeJS, or 'uv' for Python with UV"` } func init() { From 69c0be36c38bdb21ba1842a30b8d2cc42230cfdd Mon Sep 17 00:00:00 2001 From: huijiro Date: Wed, 27 Aug 2025 13:29:37 -0300 Subject: [PATCH 09/10] Removed the schema change for now --- agentuity.schema.json | 1 - 1 file changed, 1 deletion(-) diff --git a/agentuity.schema.json b/agentuity.schema.json index f8452cee..c2617378 100644 --- a/agentuity.schema.json +++ b/agentuity.schema.json @@ -170,7 +170,6 @@ }, "runtime": { "type": "string", - "enum": ["nodejs", "bunjs", "uv"], "description": "The runtime environment" }, "agents": { From 3c333eb8eee298d38aa9222f99db6ac74cfdd65d Mon Sep 17 00:00:00 2001 From: huijiro Date: Wed, 27 Aug 2025 13:46:31 -0300 Subject: [PATCH 10/10] Return python copatibility --- internal/bundler/bundler.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/internal/bundler/bundler.go b/internal/bundler/bundler.go index 570579d2..9fe579b3 100644 --- a/internal/bundler/bundler.go +++ b/internal/bundler/bundler.go @@ -158,7 +158,7 @@ func detectPackageManager(projectDir string) string { // This function returns the base command without CI-specific modifications func jsInstallCommandSpec(projectDir string) (string, []string, error) { packageManager := detectPackageManager(projectDir) - + switch packageManager { case "pnpm": return "pnpm", []string{"install", "--prod", "--ignore-scripts", "--silent"}, nil @@ -415,6 +415,10 @@ func bundlePython(ctx BundleContext, dir string, outdir string, theproject *proj switch theproject.Bundler.Runtime { case "uv": install = exec.CommandContext(ctx.Context, "uv", "sync", "--no-dev", "--frozen", "--quiet", "--no-progress") + case "pip": + install = exec.CommandContext(ctx.Context, "uv", "pip", "install", "--quiet", "--no-progress") + case "poetry": + return fmt.Errorf("poetry is not supported yet") default: return fmt.Errorf("unsupported runtime: %s", theproject.Bundler.Runtime) }