Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions cmd/creinit/creinit.go
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,19 @@ func (h *handler) Execute(inputs Inputs) error {
}
}

// Install contracts dependencies for TypeScript projects with contracts
if selectedLanguageTemplate.Lang == TemplateLangTS && contractsGenerated {
contractsDir := filepath.Join(projectRoot, "contracts")
contractsPkg := filepath.Join(contractsDir, "package.json")
if h.pathExists(contractsPkg) {
spinner.Update("Installing contracts dependencies...")
if err := runBunInstall(h.log, contractsDir); err != nil {
spinner.Stop()
return fmt.Errorf("failed to install contracts dependencies: %w", err)
}
}
}

// Generate workflow settings
spinner.Update("Generating workflow settings...")
_, err = settings.GenerateWorkflowSettingsFile(workflowDirectory, workflowName, selectedLanguageTemplate.EntryPoint)
Expand Down Expand Up @@ -487,6 +500,11 @@ func (h *handler) generateWorkflowTemplate(workingDirectory string, template Wor
return nil
}

// Skip generated directory - TS bindings live in contracts/evm/src/generated/
if strings.HasPrefix(relPath, "generated") {
return nil
}

// If it's a directory, just create the matching directory in the working dir
if d.IsDir() {
return os.MkdirAll(filepath.Join(workingDirectory, relPath), 0o755)
Expand Down
123 changes: 122 additions & 1 deletion cmd/creinit/creinit_test.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
package creinit

import (
"crypto/sha256"
"encoding/hex"
"fmt"
"io/fs"
"os"
"os/exec"
"path/filepath"
"strings"
"testing"

"github.com/stretchr/testify/require"

"github.com/smartcontractkit/cre-cli/cmd/generate-bindings/bindings"
"github.com/smartcontractkit/cre-cli/internal/constants"
"github.com/smartcontractkit/cre-cli/internal/testutil"
"github.com/smartcontractkit/cre-cli/internal/testutil/chainsim"
Expand Down Expand Up @@ -78,6 +83,109 @@ func requireNoDirExists(t *testing.T, dirPath string) {
require.Falsef(t, fi.IsDir(), "directory %s should NOT exist", dirPath)
}

func hashDirectoryFiles(t *testing.T, dir string) map[string]string {
t.Helper()
hashes := make(map[string]string)
err := filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
if d.IsDir() {
return nil
}
data, err := os.ReadFile(path)
if err != nil {
return err
}
rel, _ := filepath.Rel(dir, path)
sum := sha256.Sum256(data)
hashes[rel] = hex.EncodeToString(sum[:])
return nil
})
require.NoError(t, err)
return hashes
}

func validateGeneratedBindingsStable(t *testing.T, projectRoot, workflowName, language string) {
t.Helper()

abiDir := filepath.Join(projectRoot, "contracts", "evm", "src", "abi")

var generatedDir string
switch language {
case "go":
generatedDir = filepath.Join(projectRoot, "contracts", "evm", "src", "generated")
case "typescript":
generatedDir = filepath.Join(projectRoot, "contracts", "evm", "ts", "generated")
default:
return
}

if _, err := os.Stat(generatedDir); os.IsNotExist(err) {
return
}

beforeHashes := hashDirectoryFiles(t, generatedDir)
require.NotEmpty(t, beforeHashes, "generated directory should not be empty")

abiFiles, err := filepath.Glob(filepath.Join(abiDir, "*.abi"))
require.NoError(t, err)
require.NotEmpty(t, abiFiles, "abi directory should contain *.abi files")

switch language {
case "go":
for _, abiFile := range abiFiles {
contractName := strings.TrimSuffix(filepath.Base(abiFile), ".abi")
entries, readErr := os.ReadDir(generatedDir)
require.NoError(t, readErr)

found := false
for _, entry := range entries {
if !entry.IsDir() {
continue
}
goFile := filepath.Join(generatedDir, entry.Name(), contractName+".go")
if _, statErr := os.Stat(goFile); statErr == nil {
err = bindings.GenerateBindings("", abiFile, entry.Name(), contractName, goFile)
require.NoError(t, err, "failed to regenerate Go bindings for %s", contractName)
found = true
break
}
}
require.True(t, found, "no matching generated directory found for contract %s", contractName)
}

case "typescript":
var generatedContracts []string
for _, abiFile := range abiFiles {
ext := filepath.Ext(abiFile)
contractName := strings.TrimSuffix(filepath.Base(abiFile), ext)
outFile := filepath.Join(generatedDir, contractName+".ts")
err = bindings.GenerateBindingsTS(abiFile, contractName, outFile)
require.NoError(t, err, "failed to regenerate TS bindings for %s", contractName)
generatedContracts = append(generatedContracts, contractName)
}
// Regenerate barrel index.ts
var indexContent string
indexContent += "// Code generated — DO NOT EDIT.\n"
for _, name := range generatedContracts {
indexContent += fmt.Sprintf("export * from './%s'\n", name)
indexContent += fmt.Sprintf("export * from './%s_mock'\n", name)
}
indexPath := filepath.Join(generatedDir, "index.ts")
require.NoError(t, os.WriteFile(indexPath, []byte(indexContent), 0o600))
}

afterHashes := hashDirectoryFiles(t, generatedDir)

require.Equal(t, len(beforeHashes), len(afterHashes), "number of generated files changed after regeneration")
for file, beforeHash := range beforeHashes {
afterHash, exists := afterHashes[file]
require.True(t, exists, "generated file %s disappeared after regeneration", file)
require.Equal(t, beforeHash, afterHash, "generated file %s changed after regeneration — template is stale", file)
}
}

// runLanguageSpecificTests runs the appropriate test suite based on the language field.
// For TypeScript: runs bun install and bun test in the workflow directory.
// For Go: runs go test ./... in the workflow directory.
Expand All @@ -96,6 +204,8 @@ func runLanguageSpecificTests(t *testing.T, workflowDir, language string) {

// runTypescriptTests executes TypeScript tests using bun.
// Follows the cre init instructions: bun install --cwd <dir> then bun test in that directory.
// For projects with contracts (e.g. TS PoR), also installs contracts dependencies so generated
// bindings can resolve @chainlink/cre-sdk.
func runTypescriptTests(t *testing.T, workflowDir string) {
t.Helper()

Expand All @@ -105,7 +215,17 @@ func runTypescriptTests(t *testing.T, workflowDir string) {
require.NoError(t, err, "bun install failed in %s:\n%s", workflowDir, string(installOutput))
t.Logf("bun install succeeded")

// Run tests
// Install contracts dependencies when contracts/package.json exists (TS PoR template)
projectRoot := filepath.Dir(workflowDir)
contractsPkg := filepath.Join(projectRoot, "contracts", "package.json")
if _, err := os.Stat(contractsPkg); err == nil {
contractsDir := filepath.Join(projectRoot, "contracts")
installCmd := exec.Command("bun", "install", "--cwd", contractsDir, "--ignore-scripts")
installOutput, err := installCmd.CombinedOutput()
require.NoError(t, err, "bun install failed in %s:\n%s", contractsDir, string(installOutput))
t.Logf("bun install in contracts succeeded")
}

testCmd := exec.Command("bun", "test")
testCmd.Dir = workflowDir
testOutput, err := testCmd.CombinedOutput()
Expand Down Expand Up @@ -256,6 +376,7 @@ func TestInitExecuteFlows(t *testing.T) {
validateInitProjectStructure(t, projectRoot, tc.expectWorkflowName, tc.expectTemplateFiles)

runLanguageSpecificTests(t, filepath.Join(projectRoot, tc.expectWorkflowName), tc.language)
validateGeneratedBindingsStable(t, projectRoot, tc.expectWorkflowName, tc.language)
})
}
}
Expand Down
4 changes: 4 additions & 0 deletions cmd/creinit/go_module_init.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,3 +82,7 @@ func runCommand(logger *zerolog.Logger, dir, command string, args ...string) err
logger.Debug().Msgf("Command succeeded: %s %v", command, args)
return nil
}

func runBunInstall(logger *zerolog.Logger, dir string) error {
return runCommand(logger, dir, "bun", "install", "--ignore-scripts")
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
},
"license": "UNLICENSED",
"dependencies": {
"@chainlink/cre-sdk": "^1.1.1",
"@chainlink/cre-sdk": "^1.1.2",
"zod": "3.25.76"
},
"devDependencies": {
Expand Down

This file was deleted.

This file was deleted.

Loading
Loading