From ace9c99e517535ccdbccb11987407f1ac9d691a4 Mon Sep 17 00:00:00 2001 From: Mairon Date: Sun, 12 Oct 2025 11:04:30 +0200 Subject: [PATCH 1/2] feat: add override support for devkit avs build target --- pkg/commands/build.go | 40 +++++++++-- pkg/commands/build_test.go | 142 +++++++++++++++++++++++++++++++++++++ 2 files changed, 177 insertions(+), 5 deletions(-) diff --git a/pkg/commands/build.go b/pkg/commands/build.go index 404e2879..7a49b808 100644 --- a/pkg/commands/build.go +++ b/pkg/commands/build.go @@ -2,6 +2,7 @@ package commands import ( "fmt" + "os" "path/filepath" "github.com/Layr-Labs/devkit-cli/config/configs" @@ -22,6 +23,10 @@ var BuildCommand = &cli.Command{ Name: "context", Usage: "Select the context to use in this command (devnet, testnet or mainnet)", }, + &cli.StringFlag{ + Name: "target", + Usage: "Override the build script target (relative or absolute path)", + }, }, common.GlobalFlags...), Action: func(cCtx *cli.Context) error { logger := common.LoggerFromContext(cCtx) @@ -81,18 +86,43 @@ var BuildCommand = &cli.Command{ logger.Debug("Project Name: %s", cfg.Config.Project.Name) logger.Debug("Building AVS components...") - // All scripts contained here - scriptsDir := filepath.Join(".devkit", "scripts") + // Resolve target script (defaults to .devkit/scripts/build) + defaultScript := filepath.Join(".devkit", "scripts", "build") + scriptPath := defaultScript + + if override := cCtx.String("target"); override != "" { + if filepath.IsAbs(override) { + scriptPath = override + } else { + scriptPath = filepath.Join(".", filepath.Clean(override)) + } + + if _, statErr := os.Stat(scriptPath); statErr != nil { + if os.IsNotExist(statErr) { + return fmt.Errorf("custom build target not found: %s", scriptPath) + } + return fmt.Errorf("failed to access custom build target %s: %w", scriptPath, statErr) + } + + logger.Info("Using custom build target: %s", scriptPath) + } - // Execute build via .devkit scripts with project name - output, err := common.CallTemplateScript(cCtx.Context, logger, dir, filepath.Join(scriptsDir, "build"), common.ExpectJSONResponse, + // Execute build via script with project name + params := [][]byte{ []byte("--image"), []byte(cfg.Config.Project.Name), []byte("--tag"), []byte(version), []byte("--lang"), []byte(language), - ) + } + + // Forward any additional positional arguments to the build script + for _, arg := range cCtx.Args().Slice() { + params = append(params, []byte(arg)) + } + + output, err := common.CallTemplateScript(cCtx.Context, logger, dir, scriptPath, common.ExpectJSONResponse, params...) if err != nil { logger.Error("Build script failed with error: %v", err) return fmt.Errorf("build failed: %w", err) diff --git a/pkg/commands/build_test.go b/pkg/commands/build_test.go index 7d5f75a6..cf324513 100644 --- a/pkg/commands/build_test.go +++ b/pkg/commands/build_test.go @@ -3,8 +3,10 @@ package commands import ( "context" "errors" + "fmt" "os" "path/filepath" + "strings" "testing" "time" @@ -204,3 +206,143 @@ echo "Mock build executed"` t.Error("Build command did not exit after context cancellation") } } + +func TestBuildCommand_CustomTarget(t *testing.T) { + tmpDir := t.TempDir() + + // Create config directory and devnet.yaml + configDir := filepath.Join(tmpDir, "config") + if err := os.MkdirAll(configDir, 0755); err != nil { + t.Fatal(err) + } + contextsDir := filepath.Join(configDir, "contexts") + if err := os.MkdirAll(contextsDir, 0755); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(filepath.Join(configDir, "config.yaml"), []byte(configs.ConfigYamls[configs.LatestVersion]), 0644); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(filepath.Join(contextsDir, "devnet.yaml"), []byte(contexts.ContextYamls[contexts.LatestVersion]), 0644); err != nil { + t.Fatal(err) + } + + // Create custom userland build script + userlandDir := filepath.Join(tmpDir, "userland") + if err := os.MkdirAll(userlandDir, 0755); err != nil { + t.Fatal(err) + } + capturePath := filepath.Join(tmpDir, "captured_args.txt") + customScript := fmt.Sprintf(`#!/bin/bash +echo "$@" > "%s" +cat <<'EOF' +{"artifact":{"artifactId":"custom-artifact","component":"custom-component"}} +EOF +`, capturePath) + customScriptPath := filepath.Join(userlandDir, "build") + if err := os.WriteFile(customScriptPath, []byte(customScript), 0755); err != nil { + t.Fatal(err) + } + + oldWd, err := os.Getwd() + if err != nil { + t.Fatal(err) + } + if err := os.Chdir(tmpDir); err != nil { + t.Fatal(err) + } + defer func() { + if err := os.Chdir(oldWd); err != nil { + t.Logf("Failed to restore original directory: %v", err) + } + }() + + app := &cli.App{ + Name: "test", + Commands: []*cli.Command{testutils.WithTestConfigAndNoopLogger(BuildCommand)}, + } + + args := []string{ + "app", "build", + "--context", devnet.DEVNET_CONTEXT, + "--target", "userland/build", + "--", + "--dockerfile", "path/to/Dockerfile", + "--context", "custom-performer", + } + + if err := app.Run(args); err != nil { + t.Fatalf("Failed to execute build command with custom target: %v", err) + } + + // Ensure custom script captured the forwarded arguments + captured, err := os.ReadFile(capturePath) + if err != nil { + t.Fatalf("Failed to read captured args: %v", err) + } + capturedArgs := strings.TrimSpace(string(captured)) + if !strings.Contains(capturedArgs, "--dockerfile path/to/Dockerfile") { + t.Fatalf("Expected forwarded args to include dockerfile flag, got: %s", capturedArgs) + } + if !strings.Contains(capturedArgs, "--context custom-performer") { + t.Fatalf("Expected forwarded args to include performer context flag, got: %s", capturedArgs) + } + + // Context file should have been updated with custom artifact info + contextBytes, err := os.ReadFile(filepath.Join(configDir, "contexts", "devnet.yaml")) + if err != nil { + t.Fatalf("Failed to read context file: %v", err) + } + contextStr := string(contextBytes) + if !strings.Contains(contextStr, "artifactId: custom-artifact") { + t.Fatalf("Expected context to include custom artifactId, got: %s", contextStr) + } + if !strings.Contains(contextStr, "component: custom-component") { + t.Fatalf("Expected context to include custom component, got: %s", contextStr) + } +} + +func TestBuildCommand_CustomTargetMissing(t *testing.T) { + tmpDir := t.TempDir() + + // Create config directory and devnet.yaml + configDir := filepath.Join(tmpDir, "config") + if err := os.MkdirAll(configDir, 0755); err != nil { + t.Fatal(err) + } + contextsDir := filepath.Join(configDir, "contexts") + if err := os.MkdirAll(contextsDir, 0755); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(filepath.Join(configDir, "config.yaml"), []byte(configs.ConfigYamls[configs.LatestVersion]), 0644); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(filepath.Join(contextsDir, "devnet.yaml"), []byte(contexts.ContextYamls[contexts.LatestVersion]), 0644); err != nil { + t.Fatal(err) + } + + oldWd, err := os.Getwd() + if err != nil { + t.Fatal(err) + } + if err := os.Chdir(tmpDir); err != nil { + t.Fatal(err) + } + defer func() { + if err := os.Chdir(oldWd); err != nil { + t.Logf("Failed to restore original directory: %v", err) + } + }() + + app := &cli.App{ + Name: "test", + Commands: []*cli.Command{testutils.WithTestConfigAndNoopLogger(BuildCommand)}, + } + + err = app.Run([]string{"app", "build", "--context", devnet.DEVNET_CONTEXT, "--target", "userland/build"}) + if err == nil { + t.Fatal("Expected error due to missing custom target, got nil") + } + if !strings.Contains(err.Error(), "custom build target not found") { + t.Fatalf("Expected missing target error, got: %v", err) + } +} From c37d042b8931f923636c0643a99ae2bb03b95c82 Mon Sep 17 00:00:00 2001 From: bdchatham Date: Tue, 14 Oct 2025 09:07:47 -0700 Subject: [PATCH 2/2] Disabling E2E & smoke tests for non-canonical-repo CI --- .github/workflows/devnet.yml | 1 + .github/workflows/e2e.yml | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/devnet.yml b/.github/workflows/devnet.yml index f6e9dd71..c9a3c434 100644 --- a/.github/workflows/devnet.yml +++ b/.github/workflows/devnet.yml @@ -16,6 +16,7 @@ env: jobs: devnet-test: + if: github.event.pull_request.head.repo.full_name == github.repository strategy: fail-fast: true runs-on: ubuntu-latest diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 4d3c993c..7b81899c 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -17,7 +17,7 @@ env: jobs: e2e: runs-on: ubuntu-latest - if: github.repository == 'Layr-Labs/devkit-cli' + if: github.event.pull_request.head.repo.full_name == github.repository steps: - name: Checkout repository uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2