From 7a017645fd7071e251c25b04413836c13b12e59f Mon Sep 17 00:00:00 2001 From: Russell Stern Date: Thu, 30 Apr 2026 10:18:10 -0400 Subject: [PATCH 1/8] Added github action to test templates that are getting added/updated --- .github/workflows/template-check-comment.yml | 183 +++++++ .github/workflows/template-check.yml | 127 +++++ scripts/check-templates.sh | 545 +++++++++++++++++++ 3 files changed, 855 insertions(+) create mode 100644 .github/workflows/template-check-comment.yml create mode 100644 .github/workflows/template-check.yml create mode 100755 scripts/check-templates.sh diff --git a/.github/workflows/template-check-comment.yml b/.github/workflows/template-check-comment.yml new file mode 100644 index 00000000..6b75fd47 --- /dev/null +++ b/.github/workflows/template-check-comment.yml @@ -0,0 +1,183 @@ +name: Template Check Comment + +# Triggered when the unprivileged "Template Check" workflow completes. +# This workflow carries pull-requests: write so it can post PR comments, +# but it never checks out or executes any external code — it only reads +# the artifact produced by the check workflow. +# +# SECURITY: workflow_run always runs on the default branch, so this workflow +# definition itself cannot be tampered with by a PR contributor. +# Artifact contents are treated as untrusted strings and sanitised before use. + +on: + workflow_run: + workflows: ["Template Check"] + types: [completed] + +permissions: + pull-requests: write + actions: read # required to download artifacts from another workflow run + +jobs: + post-comment: + runs-on: ubuntu-latest + + steps: + - name: Download results artifact + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 + with: + name: template-check-results + path: /tmp/check-results + run-id: ${{ github.event.workflow_run.id }} + github-token: ${{ secrets.GITHUB_TOKEN }} + + - name: Post or remove PR comment + uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 + with: + script: | + const fs = require('fs'); + + const read = (filename) => { + try { return fs.readFileSync(`/tmp/check-results/${filename}`, 'utf8').trim(); } + catch { return ''; } + }; + + // Validate PR number — must be a positive integer. + const prNumber = parseInt(read('pr-number.txt'), 10); + if (!Number.isInteger(prNumber) || prNumber <= 0) { + console.log('Invalid or missing PR number in artifact; skipping comment.'); + return; + } + + const exitCode = read('exit-code.txt'); + // Sanitise values that came from external (untrusted) sources. + const headRef = read('head-ref.txt').replace(/[^a-zA-Z0-9._\/-]/g, ''); + + const marker = ''; + const runUrl = `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.payload.workflow_run.id}`; + + const { data: comments } = await github.rest.issues.listComments({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: prNumber, + }); + const existing = comments.find(c => c.body.includes(marker)); + + // --------------------------------------------------------------- + // All templates passed (or no templates changed) — clean up. + // --------------------------------------------------------------- + if (exitCode === '0') { + if (existing) { + await github.rest.issues.deleteComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: existing.id, + }); + console.log('Deleted stale failure comment.'); + } + return; + } + + // --------------------------------------------------------------- + // Build the failure comment. + // --------------------------------------------------------------- + + // Parse structured results if available; fall back to raw output. + let tableRows = ''; + let summaryLine = ''; + const rawOutput = read('output.txt'); + + try { + const resultsJson = read('results.json'); + if (resultsJson) { + const data = JSON.parse(resultsJson); + summaryLine = `**${data.failed} of ${data.total} template(s) failed.**`; + + // Build a markdown table row for each failed template. + // SDK Range shows what package.json / go.mod specifies. + // Resolved shows what was actually installed/used — so a + // '^1.5.0' range resolving to '2.0.0' is immediately visible. + const failedTemplates = data.templates.filter(t => t.status !== 'pass'); + const rows = failedTemplates.map(t => { + // Sanitise all values read from the artifact. + const sanitise = s => String(s || '').replace(/[|`]/g, ' ').slice(0, 80); + const name = sanitise(t.name); + const lang = sanitise(t.language); + const range = sanitise(t.sdk_range); + const resolved = sanitise(t.sdk_resolved); + const step = sanitise(t.failure_step); + // Show a short excerpt of the first real error line + const output = (t.failure_output || '') + .split('\n') + .map(l => l.trim()) + .filter(l => l && !l.startsWith('npm warn') && !l.startsWith('>')) + .slice(0, 3) + .join(' · ') + .replace(/[|`]/g, ' ') + .slice(0, 120); + return `| \`${name}\` | ${lang} | \`${range}\` | \`${resolved}\` | ${step} | ${output} |`; + }); + + if (rows.length > 0) { + tableRows = [ + '| Template | Language | SDK Range | Resolved | Step | Error |', + '|----------|----------|-----------|----------|------|-------|', + ...rows, + ].join('\n'); + } + } + } catch (e) { + console.log('Could not parse results.json, falling back to raw output:', e.message); + } + + // If we couldn't build a table, fall back to a plain code block. + let failureSection = ''; + if (tableRows) { + failureSection = tableRows; + } else { + const resultsMatch = rawOutput.match(/={8,}\nResults:.*\n={8,}[\s\S]*/); + const excerpt = resultsMatch ? resultsMatch[0].trim() : rawOutput.trim(); + failureSection = '```\n' + excerpt.slice(0, 3000) + '\n```'; + } + + const body = [ + '## ⚠️ Template Check Failures', + '', + summaryLine || 'One or more templates changed in this PR failed validation or compilation.', + '', + '> **SDK Range** is what `package.json` / `go.mod` specifies.', + '> **Resolved** is the exact version that was installed and used.', + '> A range like `^1.5.0` resolving to `2.0.0` indicates a breaking SDK release.', + '', + failureSection, + '', + `[View full output →](${runUrl})`, + '', + '
', + 'What should I do?', + '', + '- **Fix the template:** Update the template so it compiles against the SDK version it specifies.', + '- **Pin the SDK version:** Change the range (e.g. `^1.5.0`) to an exact version (e.g. `1.5.2`) if you want to lock against a known-good release.', + '', + '
', + ].join('\n'); + + const commentBody = `${marker}\n${body}`; + + if (existing) { + await github.rest.issues.updateComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: existing.id, + body: commentBody, + }); + console.log('Updated existing failure comment.'); + } else { + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: prNumber, + body: commentBody, + }); + console.log('Posted new failure comment.'); + } diff --git a/.github/workflows/template-check.yml b/.github/workflows/template-check.yml new file mode 100644 index 00000000..fcc432bf --- /dev/null +++ b/.github/workflows/template-check.yml @@ -0,0 +1,127 @@ +name: Template Check + +on: + pull_request: + types: [opened, synchronize, reopened] + +# Non-blocking informational check — never gates merges. +# Runs only against templates changed in the PR. +# Failures are reported via a PR comment (see template-check-comment.yml). +# +# Checks performed per template: +# TypeScript: YAML validation, npm install, typecheck, cre-compile to WASM +# Go: YAML validation, go mod verify, go vet, go build (GOOS=wasip1 GOARCH=wasm) +# +# The SDK version used is captured from the installed package (TS) or go.mod (Go) +# and included in the PR comment so range-vs-resolved mismatches are visible. + +permissions: + contents: read + +jobs: + template-check: + runs-on: ubuntu-latest + + defaults: + run: + shell: bash {0} + + steps: + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + fetch-depth: 0 # full history needed for git diff against base branch + + # Go is required for Go template builds (GOOS=wasip1 GOARCH=wasm). + # GOTOOLCHAIN=auto lets Go download the exact toolchain declared in go.mod + # (e.g. "toolchain go1.24.10") if it differs from the installed version. + - name: Setup Go + uses: actions/setup-go@v5 # TODO: pin to a specific commit SHA + with: + go-version: 'stable' + cache: false # cache managed explicitly below + env: + GOTOOLCHAIN: auto + + # Node + npm are required for TypeScript template checks. + - name: Setup Node + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 + with: + node-version: '22' + + # Cache Go module downloads. Keyed on all go.sum files so any new module + # version busts the cache while unchanged templates stay fast. + - name: Cache Go modules + uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4 + with: + path: | + ~/go/pkg/mod + ~/.cache/go-build + key: ${{ runner.os }}-go-templates-${{ hashFiles('**/go.sum') }} + restore-keys: ${{ runner.os }}-go-templates- + + # Cache npm package downloads. Multiple templates sharing the same + # @chainlink/cre-sdk version (e.g. ^1.5.0) will hit the cache after + # the first install, avoiding repeated network downloads. + - name: Cache npm + uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4 + with: + path: ~/.npm + key: ${{ runner.os }}-npm-templates-${{ hashFiles('**/package.json') }} + restore-keys: ${{ runner.os }}-npm-templates- + + - name: Run template checks + id: template-check + env: + BASE_REF: ${{ github.base_ref }} + RESULTS_FILE: /tmp/template-results.json + GOTOOLCHAIN: auto + run: | + set +e + OUTPUT=$(./scripts/check-templates.sh 2>&1) + EXIT_CODE=$? + set -e + + echo "$OUTPUT" > /tmp/template-check-output.txt + + # Surface output in the Actions log regardless of pass/fail + echo "$OUTPUT" + + echo "exit_code=$EXIT_CODE" >> "$GITHUB_OUTPUT" + + - name: Save results for comment workflow + if: always() + env: + PR_NUMBER: ${{ github.event.pull_request.number }} + HEAD_REF: ${{ github.head_ref }} + EXIT_CODE: ${{ steps.template-check.outputs.exit_code || '0' }} + run: | + mkdir -p /tmp/check-results + printf '%s' "$PR_NUMBER" > /tmp/check-results/pr-number.txt + printf '%s' "$HEAD_REF" > /tmp/check-results/head-ref.txt + printf '%s' "$EXIT_CODE" > /tmp/check-results/exit-code.txt + [[ -f /tmp/template-check-output.txt ]] \ + && cp /tmp/template-check-output.txt /tmp/check-results/output.txt || true + [[ -f /tmp/template-results.json ]] \ + && cp /tmp/template-results.json /tmp/check-results/results.json || true + + - name: Upload results artifact + if: always() + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + with: + name: template-check-results + path: /tmp/check-results/ + retention-days: 7 + + # Always exit 0 — this job is informational, not a merge gate. + - name: Report result (non-blocking) + if: always() + run: | + EXIT_CODE="${{ steps.template-check.outputs.exit_code || '0' }}" + if [[ "$EXIT_CODE" == "0" ]]; then + echo "✅ All changed templates passed." + else + echo "⚠️ Some templates failed — see PR comment for details." + echo "This check is informational and does not block merging." + fi + exit 0 diff --git a/scripts/check-templates.sh b/scripts/check-templates.sh new file mode 100755 index 00000000..7fb10083 --- /dev/null +++ b/scripts/check-templates.sh @@ -0,0 +1,545 @@ +#!/bin/bash +# scripts/check-templates.sh +# Validates and compiles changed CRE templates (TypeScript + Go). +# +# Usage (CI): +# BASE_REF=main ./scripts/check-templates.sh +# +# Usage (local — specific templates): +# CHANGED_TEMPLATES="building-blocks/indexer-block-trigger/block-trigger-ts" \ +# ./scripts/check-templates.sh --verbose +# +# Usage (local — all templates): +# ./scripts/check-templates.sh --verbose +# +# Environment variables: +# BASE_REF Git base branch to diff against (e.g. "main"). +# Used to find changed templates in a PR. +# CHANGED_TEMPLATES Newline-separated list of template root dirs +# relative to the repo root. Overrides BASE_REF detection. +# VERBOSE Set to 1 for verbose output (same as --verbose/-v). +# RESULTS_FILE Path to write structured JSON results. +# Default: /tmp/template-results.json + +# -------------------------------------------------------------------------- +# Flags + +VERBOSE=false +for arg in "$@"; do + case "$arg" in + -v|--verbose) VERBOSE=true ;; + esac +done +[[ "${VERBOSE:-0}" == "1" ]] && VERBOSE=true + +# -------------------------------------------------------------------------- +# Logging helpers + +info() { echo "$@"; } +vlog() { $VERBOSE && echo "$@" || true; } + +# Capture stdout+stderr of a command into a variable, streaming in verbose mode. +# Usage: run_captured [args...] +run_captured() { + local _outvar="$1"; shift + local _tmpfile; _tmpfile=$(mktemp) + if $VERBOSE; then + "$@" 2>&1 | tee "$_tmpfile"; local _rc="${PIPESTATUS[0]}" + else + "$@" > "$_tmpfile" 2>&1; local _rc=$? + fi + printf -v "$_outvar" '%s' "$(cat "$_tmpfile")" + rm -f "$_tmpfile" + return "$_rc" +} + +# -------------------------------------------------------------------------- +# Setup + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(dirname "$SCRIPT_DIR")" +RESULTS_FILE="${RESULTS_FILE:-/tmp/template-results.json}" +JSONL_FILE=$(mktemp /tmp/cre-check-XXXXXX.jsonl) + +# Lockfile tracking — populated by check_ts_workflow before each npm install. +# Backed-up lockfiles are restored; generated ones are deleted on exit. +LOCKFILE_BACKUPS=() # "backup_path:original_path" pairs +GENERATED_LOCKFILES=() # paths to delete on exit + +cleanup() { + rm -f "$JSONL_FILE" "${_FAIL_OUT:-}" 2>/dev/null || true + + # Restore lockfiles that existed before we ran + local entry backup original + for entry in "${LOCKFILE_BACKUPS[@]+"${LOCKFILE_BACKUPS[@]}"}"; do + backup="${entry%%:*}" + original="${entry##*:}" + [[ -f "$backup" ]] && mv -f "$backup" "$original" 2>/dev/null || true + done + + # Remove lockfiles that we generated fresh + local f + for f in "${GENERATED_LOCKFILES[@]+"${GENERATED_LOCKFILES[@]}"}"; do + rm -f "$f" 2>/dev/null || true + done +} +trap cleanup EXIT INT TERM + +# -------------------------------------------------------------------------- +# YAML helpers (require python3 + PyYAML, always available on ubuntu-latest) + +# Print the value of a top-level YAML field to stdout. +yaml_field() { + local yaml_file="$1" field="$2" + python3 -c " +import yaml, sys +with open(sys.argv[1]) as f: + d = yaml.safe_load(f) or {} +val = d.get(sys.argv[2]) +if val is not None: + print(str(val).strip()) +" "$yaml_file" "$field" +} + +# Print workflow dirs (one per line) from .cre/template.yaml. +yaml_workflow_dirs() { + local yaml_file="$1" + python3 -c " +import yaml, sys +with open(sys.argv[1]) as f: + d = yaml.safe_load(f) or {} +for w in d.get('workflows', []): + print(w['dir']) +" "$yaml_file" +} + +# Validate a YAML file. For kind='template', also checks required fields. +# Prints an error message to stdout and returns 1 on failure. +validate_yaml() { + local yaml_file="$1" kind="${2:-}" + python3 -c " +import yaml, sys +kind = sys.argv[2] if len(sys.argv) > 2 else '' +try: + with open(sys.argv[1]) as f: + d = yaml.safe_load(f) + if d is None: + print('empty YAML file') + sys.exit(1) + if kind == 'template': + required = ['kind', 'id', 'title', 'language', 'category', 'workflows'] + missing = [f for f in required if f not in d] + if missing: + print(f'missing required fields: {missing}') + sys.exit(1) +except yaml.YAMLError as e: + print(f'YAML parse error: {e}') + sys.exit(1) +" "$yaml_file" "$kind" 2>&1 +} + +# -------------------------------------------------------------------------- +# Results recording +# +# Each result is one JSON line in JSONL_FILE. +# Failure output is read from _FAIL_OUT (a temp file written by run_captured). + +_FAIL_OUT=$(mktemp /tmp/cre-failout-XXXXXX.txt) + +# record_pass +record_pass() { + _write_result "$1" "$2" "$3" "$4" "pass" "" "" +} + +# record_fail +record_fail() { + _write_result "$1" "$2" "$3" "$4" "fail" "$5" "${6:-}" +} + +_write_result() { + local name="$1" lang="$2" sdk_range="$3" sdk_resolved="$4" + local status="$5" step="$6" output_file="$7" + python3 -c " +import json, sys, os +name, lang, sdk_range, sdk_resolved, status, step = sys.argv[1:7] +output_file = sys.argv[7] if len(sys.argv) > 7 else '' +output = None +if output_file and os.path.isfile(output_file): + with open(output_file) as f: + text = f.read(3000).strip() + if text: + output = text +print(json.dumps({ + 'name': name, + 'language': lang, + 'sdk_range': sdk_range, + 'sdk_resolved': sdk_resolved, + 'status': status, + 'failure_step': step or None, + 'failure_output': output, +})) +" "$name" "$lang" "$sdk_range" "$sdk_resolved" "$status" "$step" "$output_file" >> "$JSONL_FILE" +} + +finalize_results() { + python3 -c " +import json, sys, os +jsonl = sys.argv[1] +out = sys.argv[2] +templates = [] +if os.path.isfile(jsonl): + with open(jsonl) as f: + for line in f: + line = line.strip() + if line: + try: + templates.append(json.loads(line)) + except json.JSONDecodeError: + pass +passed = sum(1 for t in templates if t['status'] == 'pass') +failed = len(templates) - passed +with open(out, 'w') as f: + json.dump({'templates': templates, 'total': len(templates), + 'passed': passed, 'failed': failed}, f, indent=2) +" "$JSONL_FILE" "$RESULTS_FILE" +} + +# -------------------------------------------------------------------------- +# Detect changed template roots + +detect_template_roots() { + if [[ -n "${CHANGED_TEMPLATES:-}" ]]; then + # Use explicitly provided list (newline-separated) + while IFS= read -r dir; do + [[ -n "$dir" && -f "$REPO_ROOT/$dir/.cre/template.yaml" ]] && echo "$dir" + done <<< "$CHANGED_TEMPLATES" + return + fi + + if [[ -n "${BASE_REF:-}" ]]; then + # Derive from git diff against the PR base branch + local changed_files + changed_files=$(git -C "$REPO_ROOT" diff --name-only "origin/${BASE_REF}...HEAD" 2>/dev/null || true) + if [[ -z "$changed_files" ]]; then + return + fi + + while IFS= read -r file; do + [[ -z "$file" ]] && continue + local check_dir="$REPO_ROOT/$(dirname "$file")" + while [[ "$check_dir" != "$REPO_ROOT" && "$check_dir" != "/" ]]; do + if [[ -f "$check_dir/.cre/template.yaml" ]]; then + echo "${check_dir#"$REPO_ROOT/"}" + break + fi + check_dir="$(dirname "$check_dir")" + done + done <<< "$changed_files" + return + fi + + # Fallback: all templates (useful for local dev) + while IFS= read -r yaml; do + local template_dir + template_dir="$(dirname "$(dirname "$yaml")")" + echo "${template_dir#"$REPO_ROOT/"}" + done < <(find "$REPO_ROOT" -name "template.yaml" -path "*/.cre/template.yaml" \ + -not -path "*/node_modules/*" 2>/dev/null) +} + +# -------------------------------------------------------------------------- +# TypeScript workflow check + +# check_ts_workflow +check_ts_workflow() { + local template_dir="$1" workflow_subdir="$2" + local abs_wf="$REPO_ROOT/$template_dir/$workflow_subdir" + local pkg_json="$abs_wf/package.json" + local display="$template_dir / $workflow_subdir" + + if [[ ! -f "$pkg_json" ]]; then + vlog " ⚠️ No package.json in $workflow_subdir — skipping" + return 0 + fi + + info " 📦 $display" + + # Resolve the SDK semver range from package.json + local sdk_range sdk_resolved="unknown" + sdk_range=$(node -e " +const p = require('$pkg_json'); +const deps = Object.assign({}, p.dependencies, p.devDependencies); +process.stdout.write(deps['@chainlink/cre-sdk'] || 'unknown'); +" 2>/dev/null || echo "unknown") + + local _out="" + cd "$abs_wf" + + # 1. Validate workflow.yaml if present + if [[ -f "workflow.yaml" ]]; then + local yaml_err + if ! yaml_err=$(validate_yaml "workflow.yaml"); then + info " ❌ workflow.yaml invalid: $yaml_err" + printf '%s' "$yaml_err" > "$_FAIL_OUT" + record_fail "$display" "typescript" "$sdk_range" "$sdk_resolved" "yaml-validation" "$_FAIL_OUT" + return 0 + fi + vlog " ✅ workflow.yaml valid" + fi + + # 2. npm install — track package-lock.json so we can restore it on exit. + # Use git to distinguish committed lockfiles (restore) from untracked/absent + # ones (delete). This correctly handles leftovers from previous script runs. + local _wf_rel="${abs_wf#"$REPO_ROOT/"}" + if git -C "$REPO_ROOT" ls-files --error-unmatch "$_wf_rel/package-lock.json" &>/dev/null; then + # File is committed to git — back up its current content to restore later + cp package-lock.json package-lock.json.__cre_bak + LOCKFILE_BACKUPS+=("$(pwd)/package-lock.json.__cre_bak:$(pwd)/package-lock.json") + else + # File is not tracked — mark for deletion after the check + GENERATED_LOCKFILES+=("$(pwd)/package-lock.json") + fi + vlog " Installing dependencies (SDK range: $sdk_range)..." + if ! run_captured _out npm install --no-audit --fund=false; then + printf '%s' "$_out" > "$_FAIL_OUT" + info " ❌ npm install failed" + record_fail "$display" "typescript" "$sdk_range" "$sdk_resolved" "npm install" "$_FAIL_OUT" + return 0 + fi + + # 3. Capture resolved SDK version + if [[ -f "node_modules/@chainlink/cre-sdk/package.json" ]]; then + sdk_resolved=$(node -e \ + "process.stdout.write(require('./node_modules/@chainlink/cre-sdk/package.json').version)" \ + 2>/dev/null || echo "unknown") + fi + vlog " SDK: $sdk_range → resolved $sdk_resolved" + + # 4. Typecheck (if script exists) + local has_typecheck + has_typecheck=$(node -e \ + "const s=(require('./package.json').scripts||{}); process.stdout.write(s.typecheck?'yes':'no')" \ + 2>/dev/null || echo "no") + if [[ "$has_typecheck" == "yes" ]]; then + vlog " Running typecheck..." + if ! run_captured _out npm run typecheck --silent; then + printf '%s' "$_out" > "$_FAIL_OUT" + info " ❌ typecheck failed (SDK $sdk_resolved)" + record_fail "$display" "typescript" "$sdk_range" "$sdk_resolved" "typecheck" "$_FAIL_OUT" + return 0 + fi + vlog " ✅ typecheck passed" + else + vlog " ⚠️ No typecheck script — skipping" + fi + + # 5. cre-compile (if main.ts exists) + if [[ -f "main.ts" ]]; then + vlog " Running cre-compile..." + if ! run_captured _out npx --no cre-compile main.ts; then + printf '%s' "$_out" > "$_FAIL_OUT" + info " ❌ cre-compile failed (SDK $sdk_resolved)" + record_fail "$display" "typescript" "$sdk_range" "$sdk_resolved" "cre-compile" "$_FAIL_OUT" + # Clean up any partial build artifacts + rm -f main.js main.wasm + return 0 + fi + rm -f main.js main.wasm + vlog " ✅ cre-compile passed" + info " ✅ $display (SDK $sdk_resolved)" + else + vlog " ⚠️ No main.ts — typecheck only" + info " ✅ $display (SDK $sdk_resolved, typecheck only)" + fi + + record_pass "$display" "typescript" "$sdk_range" "$sdk_resolved" +} + +# -------------------------------------------------------------------------- +# Go template check + +# check_go_template +check_go_template() { + local template_dir="$1" + local abs_template="$REPO_ROOT/$template_dir" + local display="$template_dir" + + info " 🔧 $display" + + if [[ ! -f "$abs_template/go.mod" ]]; then + vlog " ⚠️ No go.mod — skipping" + return 0 + fi + + # Resolve cre-sdk-go version from go.mod (first require line for the core module) + local sdk_version + sdk_version=$(grep -m1 'github\.com/smartcontractkit/cre-sdk-go ' "$abs_template/go.mod" \ + | awk '{print $2}') + sdk_version="${sdk_version:-unknown}" + + local _out="" + cd "$abs_template" + + # 1. Validate workflow.yaml files (one per workflow dir) + local wf_dir yaml_err + while IFS= read -r wf_dir; do + [[ -z "$wf_dir" ]] && continue + local wf_yaml="$wf_dir/workflow.yaml" + if [[ -f "$wf_yaml" ]]; then + if ! yaml_err=$(validate_yaml "$wf_yaml"); then + info " ❌ $wf_yaml invalid: $yaml_err" + printf '%s' "$yaml_err" > "$_FAIL_OUT" + record_fail "$display" "go" "$sdk_version" "$sdk_version" "yaml-validation" "$_FAIL_OUT" + return 0 + fi + vlog " ✅ $wf_yaml valid" + fi + done <<< "$(yaml_workflow_dirs "$abs_template/.cre/template.yaml")" + + # 2. go mod verify + vlog " Running go mod verify..." + if ! run_captured _out go mod verify; then + printf '%s' "$_out" > "$_FAIL_OUT" + info " ❌ go mod verify failed" + record_fail "$display" "go" "$sdk_version" "$sdk_version" "go mod verify" "$_FAIL_OUT" + return 0 + fi + vlog " ✅ go mod verify passed" + + # 3. go vet (with wasip1 build tags so constrained files are included) + vlog " Running go vet..." + if ! run_captured _out env GOOS=wasip1 GOARCH=wasm go vet ./...; then + printf '%s' "$_out" > "$_FAIL_OUT" + info " ❌ go vet failed" + record_fail "$display" "go" "$sdk_version" "$sdk_version" "go vet" "$_FAIL_OUT" + return 0 + fi + vlog " ✅ go vet passed" + + # 4. go build each workflow dir to WASM + local failed_build=false + while IFS= read -r wf_dir; do + [[ -z "$wf_dir" ]] && continue + if [[ ! -d "$wf_dir" ]]; then + vlog " ⚠️ Workflow dir '$wf_dir' not found — skipping" + continue + fi + + local wasm_out; wasm_out=$(mktemp /tmp/cre-wasm-XXXXXX.wasm) + vlog " Building ./$wf_dir → WASM..." + if ! run_captured _out env GOOS=wasip1 GOARCH=wasm go build -o "$wasm_out" "./$wf_dir"; then + rm -f "$wasm_out" + printf '%s' "$_out" > "$_FAIL_OUT" + info " ❌ go build ./$wf_dir failed (SDK $sdk_version)" + record_fail "$display" "go" "$sdk_version" "$sdk_version" "go build ./$wf_dir" "$_FAIL_OUT" + failed_build=true + break + fi + rm -f "$wasm_out" + vlog " ✅ go build ./$wf_dir passed" + done <<< "$(yaml_workflow_dirs "$abs_template/.cre/template.yaml")" + + if ! $failed_build; then + info " ✅ $display (SDK $sdk_version)" + record_pass "$display" "go" "$sdk_version" "$sdk_version" + fi +} + +# -------------------------------------------------------------------------- +# Main + +cd "$REPO_ROOT" + +TEMPLATE_ROOTS=() +while IFS= read -r root; do + [[ -n "$root" ]] && TEMPLATE_ROOTS+=("$root") +done < <(detect_template_roots | sort -u) + +if [[ ${#TEMPLATE_ROOTS[@]} -eq 0 ]]; then + info "No changed templates detected — nothing to check." + echo '{"templates":[],"total":0,"passed":0,"failed":0}' > "$RESULTS_FILE" + exit 0 +fi + +TOTAL=${#TEMPLATE_ROOTS[@]} +info "Found $TOTAL changed template(s) to check." +info "" + +IDX=0 +for template_dir in "${TEMPLATE_ROOTS[@]}"; do + IDX=$((IDX + 1)) + abs_template="$REPO_ROOT/$template_dir" + template_yaml="$abs_template/.cre/template.yaml" + + if [[ ! -f "$template_yaml" ]]; then + vlog "[$IDX/$TOTAL] Skipping $template_dir (no .cre/template.yaml)" + continue + fi + + # Validate .cre/template.yaml before anything else + local_yaml_err=$(validate_yaml "$template_yaml" "template" || true) + if [[ -n "$local_yaml_err" ]]; then + info "[$IDX/$TOTAL] $template_dir" + info " ❌ .cre/template.yaml invalid: $local_yaml_err" + printf '%s' "$local_yaml_err" > "$_FAIL_OUT" + record_fail "$template_dir" "unknown" "unknown" "unknown" "template-yaml-validation" "$_FAIL_OUT" + continue + fi + + language=$(yaml_field "$template_yaml" "language") + info "[$IDX/$TOTAL] $template_dir ($language)" + + case "$language" in + typescript) + # Check each workflow dir listed in .cre/template.yaml + while IFS= read -r wf_dir; do + [[ -z "$wf_dir" ]] && continue + check_ts_workflow "$template_dir" "$wf_dir" + done <<< "$(yaml_workflow_dirs "$template_yaml")" + ;; + go) + check_go_template "$template_dir" + ;; + *) + info " ⚠️ Unknown language '$language' — skipping" + ;; + esac + info "" +done + +# -------------------------------------------------------------------------- +# Summary + +finalize_results + +PASS_COUNT=0 FAIL_COUNT=0 +if [[ -f "$RESULTS_FILE" ]]; then + PASS_COUNT=$(python3 -c "import json; d=json.load(open('$RESULTS_FILE')); print(d['passed'])" 2>/dev/null || echo 0) + FAIL_COUNT=$(python3 -c "import json; d=json.load(open('$RESULTS_FILE')); print(d['failed'])" 2>/dev/null || echo 0) +fi + +info "========================================================" +info "Results: $PASS_COUNT passed, $FAIL_COUNT failed" +info "========================================================" + +if [[ "$FAIL_COUNT" -gt 0 ]]; then + info "" + info "Failed templates:" + python3 -c " +import json, sys +data = json.load(open('$RESULTS_FILE')) +for t in data['templates']: + if t['status'] != 'pass': + step = t.get('failure_step') or '' + print(f\" ❌ {t['name']} — {step}\") + out = t.get('failure_output') or '' + if out: + for line in out.strip().split('\n')[:20]: + print(f' {line}') +" + exit 1 +else + info "" + info "All templates passed!" + exit 0 +fi From 8ddf35efbaebf333fda4e4e3cf555a608b396764 Mon Sep 17 00:00:00 2001 From: Russell Stern Date: Thu, 30 Apr 2026 10:56:36 -0400 Subject: [PATCH 2/8] Update templates to latest sdk to pass new template check --- .../block-trigger-ts/workflow/package.json | 2 +- .../indexer-fetch-ts/workflow/package.json | 2 +- .../kv-store-ts/my-workflow/package.json | 2 +- .../my-workflow/package.json | 2 +- .../read-mvr-data-feeds-go/go.sum | 16 ++++---- .../contracts/package.json | 2 +- .../my-workflow/package.json | 2 +- .../workflow-ts/contracts/package.json | 2 +- .../workflow-ts/nav/package.json | 2 +- .../workflow-ts/por/package.json | 2 +- .../circuit-breaker-ts/contracts/package.json | 2 +- .../my-workflow/package.json | 2 +- .../cre-custom-data-feed-go/go.sum | 12 ++---- .../my-workflow/workflow_test.go | 2 + .../contracts/package.json | 2 +- .../my-workflow/package.json | 2 +- .../event-reactor-ts/contracts/package.json | 2 +- .../event-reactor-ts/my-workflow/package.json | 2 +- .../keeper-bot-ts/contracts/package.json | 2 +- .../keeper-bot-ts/my-workflow/package.json | 2 +- .../workflow-ts/contracts/package.json | 2 +- .../workflow-ts/workflow/package.json | 2 +- .../contracts/package.json | 2 +- .../market-creation/package.json | 2 +- .../market-dispute/package.json | 2 +- .../market-resolution/package.json | 2 +- .../contracts/package.json | 2 +- .../game-resolution/package.json | 2 +- .../main.ts | 41 +++++++++++-------- .../package.json | 2 +- .../bank-stablecoin-workflow/main.ts | 19 +++++---- .../bank-stablecoin-workflow/package.json | 2 +- .../ccip-transfer-workflow/main.ts | 16 +++++--- .../ccip-transfer-workflow/package.json | 2 +- .../asset-log-trigger-workflow/main.ts | 4 +- .../asset-log-trigger-workflow/package.json | 2 +- .../vault-harvester-ts/contracts/package.json | 2 +- .../my-workflow/package.json | 2 +- .../verifiable-build-ts/workflow/package.json | 2 +- 39 files changed, 91 insertions(+), 83 deletions(-) diff --git a/building-blocks/indexer-block-trigger/block-trigger-ts/workflow/package.json b/building-blocks/indexer-block-trigger/block-trigger-ts/workflow/package.json index d830e282..62b93d62 100644 --- a/building-blocks/indexer-block-trigger/block-trigger-ts/workflow/package.json +++ b/building-blocks/indexer-block-trigger/block-trigger-ts/workflow/package.json @@ -8,7 +8,7 @@ }, "license": "UNLICENSED", "dependencies": { - "@chainlink/cre-sdk": "^1.5.0" + "@chainlink/cre-sdk": "^1.6.0" }, "devDependencies": { "typescript": "5.9.3" diff --git a/building-blocks/indexer-data-fetch/indexer-fetch-ts/workflow/package.json b/building-blocks/indexer-data-fetch/indexer-fetch-ts/workflow/package.json index 3d05b770..c1419963 100644 --- a/building-blocks/indexer-data-fetch/indexer-fetch-ts/workflow/package.json +++ b/building-blocks/indexer-data-fetch/indexer-fetch-ts/workflow/package.json @@ -8,7 +8,7 @@ }, "license": "UNLICENSED", "dependencies": { - "@chainlink/cre-sdk": "^1.5.0" + "@chainlink/cre-sdk": "^1.6.0" }, "devDependencies": { "typescript": "5.9.3" diff --git a/building-blocks/kv-store/kv-store-ts/my-workflow/package.json b/building-blocks/kv-store/kv-store-ts/my-workflow/package.json index b1f73c75..7f5b27d0 100644 --- a/building-blocks/kv-store/kv-store-ts/my-workflow/package.json +++ b/building-blocks/kv-store/kv-store-ts/my-workflow/package.json @@ -8,7 +8,7 @@ }, "license": "UNLICENSED", "dependencies": { - "@chainlink/cre-sdk": "^1.5.0", + "@chainlink/cre-sdk": "^1.6.0", "@noble/hashes": "^1.7.2" }, "devDependencies": { diff --git a/building-blocks/read-data-feeds/read-data-feeds-ts/my-workflow/package.json b/building-blocks/read-data-feeds/read-data-feeds-ts/my-workflow/package.json index aa4b7cda..71e9d1bf 100644 --- a/building-blocks/read-data-feeds/read-data-feeds-ts/my-workflow/package.json +++ b/building-blocks/read-data-feeds/read-data-feeds-ts/my-workflow/package.json @@ -8,7 +8,7 @@ }, "license": "UNLICENSED", "dependencies": { - "@chainlink/cre-sdk": "^1.5.0", + "@chainlink/cre-sdk": "^1.6.0", "viem": "2.34.0", "zod": "3.25.76" }, diff --git a/building-blocks/read-data-feeds/read-mvr-data-feeds-go/go.sum b/building-blocks/read-data-feeds/read-mvr-data-feeds-go/go.sum index 7c05ea58..d9557b6d 100644 --- a/building-blocks/read-data-feeds/read-mvr-data-feeds-go/go.sum +++ b/building-blocks/read-data-feeds/read-mvr-data-feeds-go/go.sum @@ -173,14 +173,14 @@ github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible h1 github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= -github.com/smartcontractkit/chainlink-protos/cre/go v0.0.0-20250918131840-564fe2776a35 h1:hhKdzgNZT+TnohlmJODtaxlSk+jyEO79YNe8zLFtp78= -github.com/smartcontractkit/chainlink-protos/cre/go v0.0.0-20250918131840-564fe2776a35/go.mod h1:jUC52kZzEnWF9tddHh85zolKybmLpbQ1oNA4FjOHt1Q= -github.com/smartcontractkit/cre-sdk-go v1.0.0 h1:O52/QDmw/W8SJ7HQ9ASlVx7alSMGsewjL0Y8WZmgf5w= -github.com/smartcontractkit/cre-sdk-go v1.0.0/go.mod h1:CQY8hCISjctPmt8ViDVgFm4vMGLs5fYI198QhkBS++Y= -github.com/smartcontractkit/cre-sdk-go/capabilities/blockchain/evm v1.0.0-beta.0 h1:t2bzRHnqkyxvcrJKSsKPmCGLMjGO97ESgrtLCnTIEQw= -github.com/smartcontractkit/cre-sdk-go/capabilities/blockchain/evm v1.0.0-beta.0/go.mod h1:VVJ4mvA7wOU1Ic5b/vTaBMHEUysyxd0gdPPXkAu8CmY= -github.com/smartcontractkit/cre-sdk-go/capabilities/scheduler/cron v1.0.0-beta.0 h1:Tui4xQVln7Qtk3CgjBRgDfihgEaAJy2t2MofghiGIDA= -github.com/smartcontractkit/cre-sdk-go/capabilities/scheduler/cron v1.0.0-beta.0/go.mod h1:PWyrIw16It4TSyq6mDXqmSR0jF2evZRKuBxu7pK1yDw= +github.com/smartcontractkit/chainlink-protos/cre/go v0.0.0-20251103211352-4c2e6505f4d6 h1:Zmwr/k+7JM/3FRf4AcK9SqLdQ+ZmIRt3LBALa+T1Igg= +github.com/smartcontractkit/chainlink-protos/cre/go v0.0.0-20251103211352-4c2e6505f4d6/go.mod h1:jUC52kZzEnWF9tddHh85zolKybmLpbQ1oNA4FjOHt1Q= +github.com/smartcontractkit/cre-sdk-go v1.0.1-0.20251103212936-f87c1b6768b7 h1:sKGjw83NUOSyVOvYUTNdbUzd35CuIS0hDsmNb4CPrg4= +github.com/smartcontractkit/cre-sdk-go v1.0.1-0.20251103212936-f87c1b6768b7/go.mod h1:CQY8hCISjctPmt8ViDVgFm4vMGLs5fYI198QhkBS++Y= +github.com/smartcontractkit/cre-sdk-go/capabilities/blockchain/evm v1.0.0-beta.0.0.20251103212936-f87c1b6768b7 h1:riXijgLlYw3hu7YGMAuf6o1G1MYyLhqENoYqEz79HTU= +github.com/smartcontractkit/cre-sdk-go/capabilities/blockchain/evm v1.0.0-beta.0.0.20251103212936-f87c1b6768b7/go.mod h1:mbkNee1UMkB13Dab+EMwlkPbYY2Qwj5+SaOtYWagY8s= +github.com/smartcontractkit/cre-sdk-go/capabilities/scheduler/cron v1.0.0-beta.0.0.20251103212936-f87c1b6768b7 h1:mNArf+I7g6h9WLH+j65w6epY3CygfJy13HOoVRS42KQ= +github.com/smartcontractkit/cre-sdk-go/capabilities/scheduler/cron v1.0.0-beta.0.0.20251103212936-f87c1b6768b7/go.mod h1:PWyrIw16It4TSyq6mDXqmSR0jF2evZRKuBxu7pK1yDw= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/supranational/blst v0.3.16-0.20250831170142-f48500c1fdbe h1:nbdqkIGOGfUAD54q1s2YBcBz/WcsxCO9HUQ4aGV5hUw= diff --git a/building-blocks/read-data-feeds/read-mvr-data-feeds-ts/contracts/package.json b/building-blocks/read-data-feeds/read-mvr-data-feeds-ts/contracts/package.json index a7c35a63..b09d43d4 100644 --- a/building-blocks/read-data-feeds/read-mvr-data-feeds-ts/contracts/package.json +++ b/building-blocks/read-data-feeds/read-mvr-data-feeds-ts/contracts/package.json @@ -4,7 +4,7 @@ "private": true, "type": "module", "dependencies": { - "@chainlink/cre-sdk": "^1.5.0", + "@chainlink/cre-sdk": "^1.6.0", "viem": "2.34.0" } } diff --git a/building-blocks/read-data-feeds/read-mvr-data-feeds-ts/my-workflow/package.json b/building-blocks/read-data-feeds/read-mvr-data-feeds-ts/my-workflow/package.json index 3ec59d83..eb6f9922 100644 --- a/building-blocks/read-data-feeds/read-mvr-data-feeds-ts/my-workflow/package.json +++ b/building-blocks/read-data-feeds/read-mvr-data-feeds-ts/my-workflow/package.json @@ -8,7 +8,7 @@ }, "license": "UNLICENSED", "dependencies": { - "@chainlink/cre-sdk": "^1.5.0", + "@chainlink/cre-sdk": "^1.6.0", "viem": "2.34.0", "zod": "3.25.76" }, diff --git a/starter-templates/bring-your-own-data/workflow-ts/contracts/package.json b/starter-templates/bring-your-own-data/workflow-ts/contracts/package.json index a7c35a63..b09d43d4 100644 --- a/starter-templates/bring-your-own-data/workflow-ts/contracts/package.json +++ b/starter-templates/bring-your-own-data/workflow-ts/contracts/package.json @@ -4,7 +4,7 @@ "private": true, "type": "module", "dependencies": { - "@chainlink/cre-sdk": "^1.5.0", + "@chainlink/cre-sdk": "^1.6.0", "viem": "2.34.0" } } diff --git a/starter-templates/bring-your-own-data/workflow-ts/nav/package.json b/starter-templates/bring-your-own-data/workflow-ts/nav/package.json index 2c000ba5..5952239d 100644 --- a/starter-templates/bring-your-own-data/workflow-ts/nav/package.json +++ b/starter-templates/bring-your-own-data/workflow-ts/nav/package.json @@ -8,7 +8,7 @@ }, "license": "UNLICENSED", "dependencies": { - "@chainlink/cre-sdk": "^1.5.0", + "@chainlink/cre-sdk": "^1.6.0", "viem": "2.34.0", "zod": "3.25.76" }, diff --git a/starter-templates/bring-your-own-data/workflow-ts/por/package.json b/starter-templates/bring-your-own-data/workflow-ts/por/package.json index 2266674b..12d0fdee 100644 --- a/starter-templates/bring-your-own-data/workflow-ts/por/package.json +++ b/starter-templates/bring-your-own-data/workflow-ts/por/package.json @@ -8,7 +8,7 @@ }, "license": "UNLICENSED", "dependencies": { - "@chainlink/cre-sdk": "^1.5.0", + "@chainlink/cre-sdk": "^1.6.0", "viem": "2.34.0", "zod": "3.25.76" }, diff --git a/starter-templates/circuit-breaker/circuit-breaker-ts/contracts/package.json b/starter-templates/circuit-breaker/circuit-breaker-ts/contracts/package.json index a7c35a63..b09d43d4 100644 --- a/starter-templates/circuit-breaker/circuit-breaker-ts/contracts/package.json +++ b/starter-templates/circuit-breaker/circuit-breaker-ts/contracts/package.json @@ -4,7 +4,7 @@ "private": true, "type": "module", "dependencies": { - "@chainlink/cre-sdk": "^1.5.0", + "@chainlink/cre-sdk": "^1.6.0", "viem": "2.34.0" } } diff --git a/starter-templates/circuit-breaker/circuit-breaker-ts/my-workflow/package.json b/starter-templates/circuit-breaker/circuit-breaker-ts/my-workflow/package.json index 28d6d8e0..bda89733 100644 --- a/starter-templates/circuit-breaker/circuit-breaker-ts/my-workflow/package.json +++ b/starter-templates/circuit-breaker/circuit-breaker-ts/my-workflow/package.json @@ -8,7 +8,7 @@ }, "license": "UNLICENSED", "dependencies": { - "@chainlink/cre-sdk": "^1.5.0", + "@chainlink/cre-sdk": "^1.6.0", "viem": "2.34.0", "zod": "3.25.76" }, diff --git a/starter-templates/custom-data-feed/cre-custom-data-feed-go/go.sum b/starter-templates/custom-data-feed/cre-custom-data-feed-go/go.sum index 2a9e8112..733b8ad5 100644 --- a/starter-templates/custom-data-feed/cre-custom-data-feed-go/go.sum +++ b/starter-templates/custom-data-feed/cre-custom-data-feed-go/go.sum @@ -175,17 +175,13 @@ github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= github.com/smartcontractkit/chainlink-protos/cre/go v0.0.0-20250918131840-564fe2776a35 h1:hhKdzgNZT+TnohlmJODtaxlSk+jyEO79YNe8zLFtp78= github.com/smartcontractkit/chainlink-protos/cre/go v0.0.0-20250918131840-564fe2776a35/go.mod h1:jUC52kZzEnWF9tddHh85zolKybmLpbQ1oNA4FjOHt1Q= -github.com/smartcontractkit/cre-sdk-go v0.9.0 h1:MDO9HFb4tjvu4mI4gKvdO+qXP1irULxhFwlTPVBytaM= -github.com/smartcontractkit/cre-sdk-go v0.9.0/go.mod h1:CQY8hCISjctPmt8ViDVgFm4vMGLs5fYI198QhkBS++Y= +github.com/smartcontractkit/cre-sdk-go v0.10.0 h1:ZKQaTKa027R8Px30NezXauMaf0toUKeUiOEj+yc67Bg= github.com/smartcontractkit/cre-sdk-go v0.10.0/go.mod h1:CQY8hCISjctPmt8ViDVgFm4vMGLs5fYI198QhkBS++Y= -github.com/smartcontractkit/cre-sdk-go/capabilities/blockchain/evm v0.9.0 h1:0ddtacyL1aAFxIolQnbysYlJKP9FOLJc1YRFS/Z9OJA= -github.com/smartcontractkit/cre-sdk-go/capabilities/blockchain/evm v0.9.0/go.mod h1:VVJ4mvA7wOU1Ic5b/vTaBMHEUysyxd0gdPPXkAu8CmY= +github.com/smartcontractkit/cre-sdk-go/capabilities/blockchain/evm v0.10.0 h1:G0w0cLzHy/5m74IzSGz1Ynjffym4ZxLeUrRLp8EFP5w= github.com/smartcontractkit/cre-sdk-go/capabilities/blockchain/evm v0.10.0/go.mod h1:VVJ4mvA7wOU1Ic5b/vTaBMHEUysyxd0gdPPXkAu8CmY= -github.com/smartcontractkit/cre-sdk-go/capabilities/networking/http v0.9.0 h1:VTLdU4nZJ9L+4X0ql20rxQ06dt572A2kmGG2nVHRgiI= -github.com/smartcontractkit/cre-sdk-go/capabilities/networking/http v0.9.0/go.mod h1:M83m3FsM1uqVu06OO58mKUSZJjjH8OGJsmvFpFlRDxI= +github.com/smartcontractkit/cre-sdk-go/capabilities/networking/http v0.10.0 h1:nP6PVWrrTIICvjwQuFitsQecQWbqpPaYzaTEjx92eTQ= github.com/smartcontractkit/cre-sdk-go/capabilities/networking/http v0.10.0/go.mod h1:M83m3FsM1uqVu06OO58mKUSZJjjH8OGJsmvFpFlRDxI= -github.com/smartcontractkit/cre-sdk-go/capabilities/scheduler/cron v0.9.0 h1:BWqX7Cnd6VnhHEpjfrQGEajPtAwqH4MH0D7o3iEPvvU= -github.com/smartcontractkit/cre-sdk-go/capabilities/scheduler/cron v0.9.0/go.mod h1:PWyrIw16It4TSyq6mDXqmSR0jF2evZRKuBxu7pK1yDw= +github.com/smartcontractkit/cre-sdk-go/capabilities/scheduler/cron v0.10.0 h1:g7UrVaNKVEmIhVkJTk4f8raCM8Kp/RTFnAT64wqNmTY= github.com/smartcontractkit/cre-sdk-go/capabilities/scheduler/cron v0.10.0/go.mod h1:PWyrIw16It4TSyq6mDXqmSR0jF2evZRKuBxu7pK1yDw= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= diff --git a/starter-templates/custom-data-feed/cre-custom-data-feed-go/my-workflow/workflow_test.go b/starter-templates/custom-data-feed/cre-custom-data-feed-go/my-workflow/workflow_test.go index da790b91..cd4b337b 100644 --- a/starter-templates/custom-data-feed/cre-custom-data-feed-go/my-workflow/workflow_test.go +++ b/starter-templates/custom-data-feed/cre-custom-data-feed-go/my-workflow/workflow_test.go @@ -1,3 +1,5 @@ +//go:build !wasip1 + package main import ( diff --git a/starter-templates/custom-data-feed/cre-custom-data-feed-ts/contracts/package.json b/starter-templates/custom-data-feed/cre-custom-data-feed-ts/contracts/package.json index a7c35a63..b09d43d4 100644 --- a/starter-templates/custom-data-feed/cre-custom-data-feed-ts/contracts/package.json +++ b/starter-templates/custom-data-feed/cre-custom-data-feed-ts/contracts/package.json @@ -4,7 +4,7 @@ "private": true, "type": "module", "dependencies": { - "@chainlink/cre-sdk": "^1.5.0", + "@chainlink/cre-sdk": "^1.6.0", "viem": "2.34.0" } } diff --git a/starter-templates/custom-data-feed/cre-custom-data-feed-ts/my-workflow/package.json b/starter-templates/custom-data-feed/cre-custom-data-feed-ts/my-workflow/package.json index b4563379..37b9292b 100644 --- a/starter-templates/custom-data-feed/cre-custom-data-feed-ts/my-workflow/package.json +++ b/starter-templates/custom-data-feed/cre-custom-data-feed-ts/my-workflow/package.json @@ -8,7 +8,7 @@ }, "license": "UNLICENSED", "dependencies": { - "@chainlink/cre-sdk": "^1.5.0", + "@chainlink/cre-sdk": "^1.6.0", "viem": "2.34.0", "zod": "3.25.76" }, diff --git a/starter-templates/event-reactor/event-reactor-ts/contracts/package.json b/starter-templates/event-reactor/event-reactor-ts/contracts/package.json index a7c35a63..b09d43d4 100644 --- a/starter-templates/event-reactor/event-reactor-ts/contracts/package.json +++ b/starter-templates/event-reactor/event-reactor-ts/contracts/package.json @@ -4,7 +4,7 @@ "private": true, "type": "module", "dependencies": { - "@chainlink/cre-sdk": "^1.5.0", + "@chainlink/cre-sdk": "^1.6.0", "viem": "2.34.0" } } diff --git a/starter-templates/event-reactor/event-reactor-ts/my-workflow/package.json b/starter-templates/event-reactor/event-reactor-ts/my-workflow/package.json index 22bfd020..f2e47cd6 100644 --- a/starter-templates/event-reactor/event-reactor-ts/my-workflow/package.json +++ b/starter-templates/event-reactor/event-reactor-ts/my-workflow/package.json @@ -8,7 +8,7 @@ }, "license": "UNLICENSED", "dependencies": { - "@chainlink/cre-sdk": "^1.5.0", + "@chainlink/cre-sdk": "^1.6.0", "viem": "2.34.0", "zod": "3.25.76" }, diff --git a/starter-templates/keeper-bot/keeper-bot-ts/contracts/package.json b/starter-templates/keeper-bot/keeper-bot-ts/contracts/package.json index a7c35a63..b09d43d4 100644 --- a/starter-templates/keeper-bot/keeper-bot-ts/contracts/package.json +++ b/starter-templates/keeper-bot/keeper-bot-ts/contracts/package.json @@ -4,7 +4,7 @@ "private": true, "type": "module", "dependencies": { - "@chainlink/cre-sdk": "^1.5.0", + "@chainlink/cre-sdk": "^1.6.0", "viem": "2.34.0" } } diff --git a/starter-templates/keeper-bot/keeper-bot-ts/my-workflow/package.json b/starter-templates/keeper-bot/keeper-bot-ts/my-workflow/package.json index aca85e7d..ecd294a3 100644 --- a/starter-templates/keeper-bot/keeper-bot-ts/my-workflow/package.json +++ b/starter-templates/keeper-bot/keeper-bot-ts/my-workflow/package.json @@ -8,7 +8,7 @@ }, "license": "UNLICENSED", "dependencies": { - "@chainlink/cre-sdk": "^1.5.0", + "@chainlink/cre-sdk": "^1.6.0", "viem": "2.34.0", "zod": "3.25.76" }, diff --git a/starter-templates/multi-chain-token-manager/workflow-ts/contracts/package.json b/starter-templates/multi-chain-token-manager/workflow-ts/contracts/package.json index a7c35a63..b09d43d4 100644 --- a/starter-templates/multi-chain-token-manager/workflow-ts/contracts/package.json +++ b/starter-templates/multi-chain-token-manager/workflow-ts/contracts/package.json @@ -4,7 +4,7 @@ "private": true, "type": "module", "dependencies": { - "@chainlink/cre-sdk": "^1.5.0", + "@chainlink/cre-sdk": "^1.6.0", "viem": "2.34.0" } } diff --git a/starter-templates/multi-chain-token-manager/workflow-ts/workflow/package.json b/starter-templates/multi-chain-token-manager/workflow-ts/workflow/package.json index cf5db34f..1dafa9ad 100644 --- a/starter-templates/multi-chain-token-manager/workflow-ts/workflow/package.json +++ b/starter-templates/multi-chain-token-manager/workflow-ts/workflow/package.json @@ -8,7 +8,7 @@ }, "license": "UNLICENSED", "dependencies": { - "@chainlink/cre-sdk": "^1.5.0", + "@chainlink/cre-sdk": "^1.6.0", "viem": "2.34.0", "zod": "3.25.76" }, diff --git a/starter-templates/prediction-market/prediction-market-ts/contracts/package.json b/starter-templates/prediction-market/prediction-market-ts/contracts/package.json index a7c35a63..b09d43d4 100644 --- a/starter-templates/prediction-market/prediction-market-ts/contracts/package.json +++ b/starter-templates/prediction-market/prediction-market-ts/contracts/package.json @@ -4,7 +4,7 @@ "private": true, "type": "module", "dependencies": { - "@chainlink/cre-sdk": "^1.5.0", + "@chainlink/cre-sdk": "^1.6.0", "viem": "2.34.0" } } diff --git a/starter-templates/prediction-market/prediction-market-ts/market-creation/package.json b/starter-templates/prediction-market/prediction-market-ts/market-creation/package.json index a832dddf..b54b2a18 100644 --- a/starter-templates/prediction-market/prediction-market-ts/market-creation/package.json +++ b/starter-templates/prediction-market/prediction-market-ts/market-creation/package.json @@ -8,7 +8,7 @@ }, "license": "UNLICENSED", "dependencies": { - "@chainlink/cre-sdk": "^1.5.0", + "@chainlink/cre-sdk": "^1.6.0", "viem": "2.34.0", "zod": "3.25.76" }, diff --git a/starter-templates/prediction-market/prediction-market-ts/market-dispute/package.json b/starter-templates/prediction-market/prediction-market-ts/market-dispute/package.json index 47e2358e..45aa5c20 100644 --- a/starter-templates/prediction-market/prediction-market-ts/market-dispute/package.json +++ b/starter-templates/prediction-market/prediction-market-ts/market-dispute/package.json @@ -8,7 +8,7 @@ }, "license": "UNLICENSED", "dependencies": { - "@chainlink/cre-sdk": "^1.5.0", + "@chainlink/cre-sdk": "^1.6.0", "viem": "2.34.0", "zod": "3.25.76" }, diff --git a/starter-templates/prediction-market/prediction-market-ts/market-resolution/package.json b/starter-templates/prediction-market/prediction-market-ts/market-resolution/package.json index aa673627..f44be7fe 100644 --- a/starter-templates/prediction-market/prediction-market-ts/market-resolution/package.json +++ b/starter-templates/prediction-market/prediction-market-ts/market-resolution/package.json @@ -8,7 +8,7 @@ }, "license": "UNLICENSED", "dependencies": { - "@chainlink/cre-sdk": "^1.5.0", + "@chainlink/cre-sdk": "^1.6.0", "viem": "2.34.0", "zod": "3.25.76" }, diff --git a/starter-templates/sports-resolution/sports-resolution-ts/contracts/package.json b/starter-templates/sports-resolution/sports-resolution-ts/contracts/package.json index a7c35a63..b09d43d4 100644 --- a/starter-templates/sports-resolution/sports-resolution-ts/contracts/package.json +++ b/starter-templates/sports-resolution/sports-resolution-ts/contracts/package.json @@ -4,7 +4,7 @@ "private": true, "type": "module", "dependencies": { - "@chainlink/cre-sdk": "^1.5.0", + "@chainlink/cre-sdk": "^1.6.0", "viem": "2.34.0" } } diff --git a/starter-templates/sports-resolution/sports-resolution-ts/game-resolution/package.json b/starter-templates/sports-resolution/sports-resolution-ts/game-resolution/package.json index 5186cd4b..df394fe2 100644 --- a/starter-templates/sports-resolution/sports-resolution-ts/game-resolution/package.json +++ b/starter-templates/sports-resolution/sports-resolution-ts/game-resolution/package.json @@ -8,7 +8,7 @@ }, "license": "UNLICENSED", "dependencies": { - "@chainlink/cre-sdk": "^1.5.0", + "@chainlink/cre-sdk": "^1.6.0", "viem": "2.34.0", "zod": "3.25.76" }, diff --git a/starter-templates/stablecoin-ace-ccip/bank-stablecoin-por-ace-ccip-workflow/main.ts b/starter-templates/stablecoin-ace-ccip/bank-stablecoin-por-ace-ccip-workflow/main.ts index e53ec61e..c31f7147 100644 --- a/starter-templates/stablecoin-ace-ccip/bank-stablecoin-por-ace-ccip-workflow/main.ts +++ b/starter-templates/stablecoin-ace-ccip/bank-stablecoin-por-ace-ccip-workflow/main.ts @@ -1,14 +1,17 @@ -import { +import { bytesToHex, + consensusIdenticalAggregation, cre, + EVMClient, getNetwork, type HTTPPayload, hexToBase64, + HTTPClient, + HTTPCapability, Runner, type Runtime, type NodeRuntime, TxStatus, - consensusMedianAggregation, } from '@chainlink/cre-sdk' import { encodeAbiParameters, parseAbiParameters, encodeFunctionData, decodeFunctionResult, getAddress, parseUnits } from 'viem' import { z } from 'zod' @@ -33,6 +36,8 @@ const configSchema = z.object({ type Config = z.infer +type PorReserveData = { totalReserve: number; lastUpdated: string } + // ======================================== // PAYLOAD SCHEMA // ======================================== @@ -99,7 +104,7 @@ const validateProofOfReserve = ( // For mock data (file:// URL), use hardcoded values // In production, this would fetch from a real PoR API endpoint - let reserveData: { totalReserve: number; lastUpdated: string } + let reserveData: PorReserveData if (config.porApiUrl.startsWith('file://')) { // Mock PoR data (matching mock-por-response.json) @@ -111,8 +116,8 @@ const validateProofOfReserve = ( } else { // Fetch from real PoR API in node mode reserveData = runtime.runInNodeMode( - (nodeRuntime: NodeRuntime) => { - const httpClient = new cre.capabilities.HTTPClient() + (nodeRuntime: NodeRuntime) => { + const httpClient = new HTTPClient() const response = httpClient.sendRequest(nodeRuntime, { url: config.porApiUrl, method: 'GET', @@ -124,7 +129,7 @@ const validateProofOfReserve = ( lastUpdated: data.lastUpdated, } }, - consensusMedianAggregation() + consensusIdenticalAggregation(), )().result() } @@ -163,7 +168,7 @@ const validateProofOfReserve = ( */ const mintWithACE = ( runtime: Runtime, - evmClient: cre.capabilities.EVMClient, + evmClient: EVMClient, beneficiary: string, mintRecipient: string, amount: bigint, @@ -231,13 +236,13 @@ const mintWithACE = ( // We need to check the actual execution result from Forwarder events // For now, check if txStatus is success and errorMessage is empty if (txStatus !== TxStatus.SUCCESS) { - const errorMsg = resp.errorMessage || txStatus - + const errorMsg = resp.errorMessage ?? String(txStatus) + // Check if it's a PolicyRunRejected error if (errorMsg.includes('PolicyRunRejected') || errorMsg.includes('blacklisted')) { throw new Error(`[ACE REJECTED] Address ${beneficiary} is blacklisted`) } - + throw new Error(`Failed to mint: ${errorMsg}`) } @@ -265,7 +270,7 @@ const mintWithACE = ( */ const transferWithACE = ( runtime: Runtime, - evmClient: cre.capabilities.EVMClient, + evmClient: EVMClient, sender: string, beneficiary: string, amount: bigint, @@ -322,13 +327,13 @@ const transferWithACE = ( const txStatus = resp.txStatus if (txStatus !== TxStatus.SUCCESS) { - const errorMsg = resp.errorMessage || txStatus - + const errorMsg = resp.errorMessage ?? String(txStatus) + // Check if it's a PolicyRunRejected error if (errorMsg.includes('PolicyRunRejected') || errorMsg.includes('blacklisted')) { throw new Error(`[ACE REJECTED] Beneficiary ${beneficiary} is blacklisted`) } - + throw new Error(`Failed to initiate CCIP transfer: ${errorMsg}`) } @@ -344,7 +349,7 @@ const transferWithACE = ( // ======================================== // HTTP TRIGGER HANDLER // ======================================== -const onHTTPTrigger = (runtime: Runtime, payload: HTTPPayload): object => { +const onHTTPTrigger = (runtime: Runtime, payload: HTTPPayload) => { runtime.log('=== Phase 3: PoR + ACE + CCIP Workflow ===') // Require payload @@ -375,7 +380,7 @@ const onHTTPTrigger = (runtime: Runtime, payload: HTTPPayload): object = throw new Error('Sepolia network not found') } - const evmClient = new cre.capabilities.EVMClient(network.chainSelector.selector) + const evmClient = new EVMClient(network.chainSelector.selector) // Convert amount to wei const amountWei = parseUnits(parsedPayload.amount, runtime.config.decimals) @@ -510,7 +515,7 @@ const onHTTPTrigger = (runtime: Runtime, payload: HTTPPayload): object = runtime.log(` Verify on-chain to confirm actual results`) runtime.log(`\nResult: ${safeJsonStringify(result)}`) - return JSON.stringify(result) + return result } catch (error: any) { runtime.log(`❌ Workflow error: ${error.message}`) @@ -522,7 +527,7 @@ const onHTTPTrigger = (runtime: Runtime, payload: HTTPPayload): object = // WORKFLOW INITIALIZATION // ======================================== const initWorkflow = (config: Config) => { - const httpTrigger = new cre.capabilities.HTTPCapability() + const httpTrigger = new HTTPCapability() return [ cre.handler(httpTrigger.trigger({}), onHTTPTrigger), diff --git a/starter-templates/stablecoin-ace-ccip/bank-stablecoin-por-ace-ccip-workflow/package.json b/starter-templates/stablecoin-ace-ccip/bank-stablecoin-por-ace-ccip-workflow/package.json index 9b8a27b2..a3543711 100644 --- a/starter-templates/stablecoin-ace-ccip/bank-stablecoin-por-ace-ccip-workflow/package.json +++ b/starter-templates/stablecoin-ace-ccip/bank-stablecoin-por-ace-ccip-workflow/package.json @@ -8,7 +8,7 @@ }, "license": "UNLICENSED", "dependencies": { - "@chainlink/cre-sdk": "^1.5.0", + "@chainlink/cre-sdk": "^1.6.0", "@openzeppelin/contracts": "5.4.0", "viem": "2.34.0", "zod": "3.25.76" diff --git a/starter-templates/stablecoin-ace-ccip/bank-stablecoin-workflow/main.ts b/starter-templates/stablecoin-ace-ccip/bank-stablecoin-workflow/main.ts index e00d2317..4d4f8489 100644 --- a/starter-templates/stablecoin-ace-ccip/bank-stablecoin-workflow/main.ts +++ b/starter-templates/stablecoin-ace-ccip/bank-stablecoin-workflow/main.ts @@ -1,9 +1,11 @@ -import { +import { bytesToHex, cre, + EVMClient, getNetwork, type HTTPPayload, hexToBase64, + HTTPCapability, Runner, type Runtime, TxStatus, @@ -50,7 +52,7 @@ const safeJsonStringify = (obj: any): string => const submitBankInstruction = ( runtime: Runtime, - evmClient: cre.capabilities.EVMClient, + evmClient: EVMClient, instructionType: number, account: string, amount: bigint, @@ -112,7 +114,7 @@ const submitBankInstruction = ( return txHashHex } -const processBankInstruction = (runtime: Runtime, evmClient: cre.capabilities.EVMClient, swiftData: SWIFTPayload): string => { +const processBankInstruction = (runtime: Runtime, evmClient: EVMClient, swiftData: SWIFTPayload): string => { runtime.log(`Processing ${swiftData.instructionCode} instruction from bank: ${swiftData.bankReference}`) // Convert amount from string to scaled bigint (e.g., "1000.00" -> 1000000000000000000000) @@ -142,7 +144,7 @@ const processBankInstruction = (runtime: Runtime, evmClient: cre.capabil return `${swiftData.instructionCode} instruction processed: ${swiftData.amount} ${swiftData.currency}` } -const onHTTPTrigger = (runtime: Runtime, evmClient: cre.capabilities.EVMClient, payload: HTTPPayload): string => { +const onHTTPTrigger = (runtime: Runtime, evmClient: EVMClient, payload: HTTPPayload): string => { runtime.log('Raw HTTP trigger received') // Require payload @@ -169,7 +171,7 @@ const onHTTPTrigger = (runtime: Runtime, evmClient: cre.capabilities.EVM } const initWorkflow = (config: Config) => { - const httpTrigger = new cre.capabilities.HTTPCapability() + const httpTrigger = new HTTPCapability() // Initialize EVM client for the configured chain const network = getNetwork({ @@ -184,12 +186,11 @@ const initWorkflow = (config: Config) => { ) } - const evmClient = new cre.capabilities.EVMClient(network.chainSelector.selector) + const evmClient = new EVMClient(network.chainSelector.selector) return [ - cre.handler(httpTrigger.trigger({}), (runtime, payload) => - onHTTPTrigger(runtime, evmClient, payload) - ), + cre.handler(httpTrigger.trigger({}), (runtime: Runtime, payload: HTTPPayload) => + onHTTPTrigger(runtime, evmClient, payload)), ] } diff --git a/starter-templates/stablecoin-ace-ccip/bank-stablecoin-workflow/package.json b/starter-templates/stablecoin-ace-ccip/bank-stablecoin-workflow/package.json index d6ea651a..93906fcc 100644 --- a/starter-templates/stablecoin-ace-ccip/bank-stablecoin-workflow/package.json +++ b/starter-templates/stablecoin-ace-ccip/bank-stablecoin-workflow/package.json @@ -8,7 +8,7 @@ }, "license": "UNLICENSED", "dependencies": { - "@chainlink/cre-sdk": "^1.5.0", + "@chainlink/cre-sdk": "^1.6.0", "@openzeppelin/contracts": "5.4.0", "viem": "2.34.0", "zod": "3.25.76" diff --git a/starter-templates/stablecoin-ace-ccip/ccip-transfer-workflow/main.ts b/starter-templates/stablecoin-ace-ccip/ccip-transfer-workflow/main.ts index de105ae6..a17c911f 100644 --- a/starter-templates/stablecoin-ace-ccip/ccip-transfer-workflow/main.ts +++ b/starter-templates/stablecoin-ace-ccip/ccip-transfer-workflow/main.ts @@ -1,9 +1,11 @@ -import { +import { bytesToHex, cre, + EVMClient, getNetwork, type HTTPPayload, hexToBase64, + HTTPCapability, Runner, type Runtime, TxStatus, @@ -53,7 +55,7 @@ const safeJsonStringify = (obj: any): string => const submitCCIPTransfer = ( runtime: Runtime, - evmClient: cre.capabilities.EVMClient, + evmClient: EVMClient, consumerAddress: string, sender: string, recipient: string, @@ -115,7 +117,7 @@ const submitCCIPTransfer = ( return txHashHex } -const processCCIPTransfer = (runtime: Runtime, evmClient: cre.capabilities.EVMClient, transferData: TransferPayload): string => { +const processCCIPTransfer = (runtime: Runtime, evmClient: EVMClient, transferData: TransferPayload): string => { runtime.log(`Processing CCIP transfer from bank: ${transferData.bankReference}`) // Look up source and destination chain configs @@ -183,7 +185,7 @@ const onHTTPTrigger = (runtime: Runtime, payload: HTTPPayload): string = throw new Error(`Network not found for source chain: ${transferData.sourceChain}`) } - const evmClient = new cre.capabilities.EVMClient(sourceNetwork.chainSelector.selector) + const evmClient = new EVMClient(sourceNetwork.chainSelector.selector) return processCCIPTransfer(runtime, evmClient, transferData) } catch (error) { @@ -193,10 +195,12 @@ const onHTTPTrigger = (runtime: Runtime, payload: HTTPPayload): string = } const initWorkflow = (config: Config) => { - const httpTrigger = new cre.capabilities.HTTPCapability() + const httpTrigger = new HTTPCapability() return [ - cre.handler(httpTrigger.trigger({}), onHTTPTrigger), + cre.handler(httpTrigger.trigger({}), (runtime: Runtime, payload: HTTPPayload) => + onHTTPTrigger(runtime, payload), + ), ] } diff --git a/starter-templates/stablecoin-ace-ccip/ccip-transfer-workflow/package.json b/starter-templates/stablecoin-ace-ccip/ccip-transfer-workflow/package.json index 72902f5b..4eba1f0e 100644 --- a/starter-templates/stablecoin-ace-ccip/ccip-transfer-workflow/package.json +++ b/starter-templates/stablecoin-ace-ccip/ccip-transfer-workflow/package.json @@ -8,7 +8,7 @@ }, "license": "UNLICENSED", "dependencies": { - "@chainlink/cre-sdk": "^1.5.0", + "@chainlink/cre-sdk": "^1.6.0", "viem": "2.34.0" }, "devDependencies": { diff --git a/starter-templates/tokenized-asset-servicing/asset-log-trigger-workflow/main.ts b/starter-templates/tokenized-asset-servicing/asset-log-trigger-workflow/main.ts index 4e499ac3..16cc977f 100644 --- a/starter-templates/tokenized-asset-servicing/asset-log-trigger-workflow/main.ts +++ b/starter-templates/tokenized-asset-servicing/asset-log-trigger-workflow/main.ts @@ -82,8 +82,8 @@ const postData = (sendRequester: HTTPSendRequester, config: Config, assetParams: "Content-Type": "application/json", }, cacheSettings: { - readFromCache: true, // Enable reading from cache - maxAgeMs: 60000, // Accept cached responses up to 60 seconds old + store: true, + maxAge: '60s', }, } diff --git a/starter-templates/tokenized-asset-servicing/asset-log-trigger-workflow/package.json b/starter-templates/tokenized-asset-servicing/asset-log-trigger-workflow/package.json index a7cf19b9..8b221455 100644 --- a/starter-templates/tokenized-asset-servicing/asset-log-trigger-workflow/package.json +++ b/starter-templates/tokenized-asset-servicing/asset-log-trigger-workflow/package.json @@ -8,7 +8,7 @@ }, "license": "UNLICENSED", "dependencies": { - "@chainlink/cre-sdk": "^1.5.0", + "@chainlink/cre-sdk": "^1.6.0", "viem": "2.34.0", "zod": "3.25.76" }, diff --git a/starter-templates/vault-harvester/vault-harvester-ts/contracts/package.json b/starter-templates/vault-harvester/vault-harvester-ts/contracts/package.json index a7c35a63..b09d43d4 100644 --- a/starter-templates/vault-harvester/vault-harvester-ts/contracts/package.json +++ b/starter-templates/vault-harvester/vault-harvester-ts/contracts/package.json @@ -4,7 +4,7 @@ "private": true, "type": "module", "dependencies": { - "@chainlink/cre-sdk": "^1.5.0", + "@chainlink/cre-sdk": "^1.6.0", "viem": "2.34.0" } } diff --git a/starter-templates/vault-harvester/vault-harvester-ts/my-workflow/package.json b/starter-templates/vault-harvester/vault-harvester-ts/my-workflow/package.json index 1a44b5eb..d89de1cc 100644 --- a/starter-templates/vault-harvester/vault-harvester-ts/my-workflow/package.json +++ b/starter-templates/vault-harvester/vault-harvester-ts/my-workflow/package.json @@ -8,7 +8,7 @@ }, "license": "UNLICENSED", "dependencies": { - "@chainlink/cre-sdk": "^1.5.0", + "@chainlink/cre-sdk": "^1.6.0", "viem": "2.34.0", "zod": "3.25.76" }, diff --git a/starter-templates/verifiable-build/verifiable-build-ts/workflow/package.json b/starter-templates/verifiable-build/verifiable-build-ts/workflow/package.json index dfe126d8..65487e3b 100644 --- a/starter-templates/verifiable-build/verifiable-build-ts/workflow/package.json +++ b/starter-templates/verifiable-build/verifiable-build-ts/workflow/package.json @@ -8,7 +8,7 @@ }, "license": "UNLICENSED", "dependencies": { - "@chainlink/cre-sdk": "^1.5.0" + "@chainlink/cre-sdk": "^1.6.0" }, "devDependencies": { "typescript": "5.9.3" From 5e4421e70e92e0ae151437f4d39efa368bb53698 Mon Sep 17 00:00:00 2001 From: Russell Stern Date: Thu, 30 Apr 2026 11:22:18 -0400 Subject: [PATCH 3/8] Added specific setup-go hash for action --- .github/workflows/template-check.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/template-check.yml b/.github/workflows/template-check.yml index fcc432bf..703c2f90 100644 --- a/.github/workflows/template-check.yml +++ b/.github/workflows/template-check.yml @@ -36,7 +36,7 @@ jobs: # GOTOOLCHAIN=auto lets Go download the exact toolchain declared in go.mod # (e.g. "toolchain go1.24.10") if it differs from the installed version. - name: Setup Go - uses: actions/setup-go@v5 # TODO: pin to a specific commit SHA + uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5.6.0 with: go-version: 'stable' cache: false # cache managed explicitly below From 192c6ee4288e4eb8ee93adc36bb312251ac10e61 Mon Sep 17 00:00:00 2001 From: Russell Stern Date: Thu, 30 Apr 2026 11:59:14 -0400 Subject: [PATCH 4/8] Added bun to the template check action --- .github/workflows/template-check.yml | 5 +++++ scripts/check-templates.sh | 32 +++++++++++++++++++++++++++- 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/.github/workflows/template-check.yml b/.github/workflows/template-check.yml index 703c2f90..64614096 100644 --- a/.github/workflows/template-check.yml +++ b/.github/workflows/template-check.yml @@ -49,6 +49,11 @@ jobs: with: node-version: '22' + # cre-compile is shipped with a bun shebang; npx invokes it as /usr/bin/env bun. + # Match local dev machines where Bun is installed alongside Node. + - name: Setup Bun + uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2 + # Cache Go module downloads. Keyed on all go.sum files so any new module # version busts the cache while unchanged templates stay fast. - name: Cache Go modules diff --git a/scripts/check-templates.sh b/scripts/check-templates.sh index 7fb10083..b8cd5940 100755 --- a/scripts/check-templates.sh +++ b/scripts/check-templates.sh @@ -307,6 +307,28 @@ process.stdout.write(deps['@chainlink/cre-sdk'] || 'unknown'); return 0 fi + # 2b. npm install in template contracts/ when present (generated bindings import viem / cre-sdk). + local contracts_dir="$REPO_ROOT/$template_dir/contracts" + if [[ -f "$contracts_dir/package.json" ]]; then + vlog " Installing contracts dependencies..." + cd "$contracts_dir" + local _contracts_rel="${contracts_dir#"$REPO_ROOT/"}" + if git -C "$REPO_ROOT" ls-files --error-unmatch "$_contracts_rel/package-lock.json" &>/dev/null; then + cp package-lock.json package-lock.json.__cre_bak + LOCKFILE_BACKUPS+=("$(pwd)/package-lock.json.__cre_bak:$(pwd)/package-lock.json") + else + GENERATED_LOCKFILES+=("$(pwd)/package-lock.json") + fi + if ! run_captured _out npm install --no-audit --fund=false; then + printf '%s' "$_out" > "$_FAIL_OUT" + info " ❌ npm install (contracts) failed" + record_fail "$display" "typescript" "$sdk_range" "$sdk_resolved" "npm install (contracts)" "$_FAIL_OUT" + cd "$abs_wf" + return 0 + fi + cd "$abs_wf" + fi + # 3. Capture resolved SDK version if [[ -f "node_modules/@chainlink/cre-sdk/package.json" ]]; then sdk_resolved=$(node -e \ @@ -334,9 +356,17 @@ process.stdout.write(deps['@chainlink/cre-sdk'] || 'unknown'); fi # 5. cre-compile (if main.ts exists) + # Prefer `bun x` so the Bun runtime is used explicitly (cre-compile's bin uses a bun shebang; + # `npx` on some Linux/npm combinations does not invoke it reliably). if [[ -f "main.ts" ]]; then vlog " Running cre-compile..." - if ! run_captured _out npx --no cre-compile main.ts; then + local _compile_cmd + if command -v bun >/dev/null 2>&1; then + _compile_cmd=(bun x cre-compile main.ts) + else + _compile_cmd=(npx --no cre-compile main.ts) + fi + if ! run_captured _out "${_compile_cmd[@]}"; then printf '%s' "$_out" > "$_FAIL_OUT" info " ❌ cre-compile failed (SDK $sdk_resolved)" record_fail "$display" "typescript" "$sdk_range" "$sdk_resolved" "cre-compile" "$_FAIL_OUT" From cc69a2794a5fb914f6f60ab0c3e002dbdad113f8 Mon Sep 17 00:00:00 2001 From: Russell Stern Date: Mon, 4 May 2026 10:55:15 -0400 Subject: [PATCH 5/8] Added concurrency check and switched to bun instead of npm --- .github/workflows/template-check-comment.yml | 161 ++----------------- .github/workflows/template-check.yml | 4 + scripts/check-templates.sh | 61 ++++--- scripts/template-check-comment.js | 154 ++++++++++++++++++ 4 files changed, 197 insertions(+), 183 deletions(-) create mode 100644 scripts/template-check-comment.js diff --git a/.github/workflows/template-check-comment.yml b/.github/workflows/template-check-comment.yml index 6b75fd47..f4e1649a 100644 --- a/.github/workflows/template-check-comment.yml +++ b/.github/workflows/template-check-comment.yml @@ -1,12 +1,12 @@ name: Template Check Comment # Triggered when the unprivileged "Template Check" workflow completes. -# This workflow carries pull-requests: write so it can post PR comments, -# but it never checks out or executes any external code — it only reads -# the artifact produced by the check workflow. +# This workflow has pull-requests: write so it can post PR comments. +# It checks out the default branch (only) to load scripts/template-check-comment.js, +# then reads the artifact produced by the check workflow. # # SECURITY: workflow_run always runs on the default branch, so this workflow -# definition itself cannot be tampered with by a PR contributor. +# definition and checked-out script cannot be tampered with by a PR contributor. # Artifact contents are treated as untrusted strings and sanitised before use. on: @@ -15,6 +15,7 @@ on: types: [completed] permissions: + contents: read pull-requests: write actions: read # required to download artifacts from another workflow run @@ -23,6 +24,9 @@ jobs: runs-on: ubuntu-latest steps: + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - name: Download results artifact uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 with: @@ -35,149 +39,6 @@ jobs: uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 with: script: | - const fs = require('fs'); - - const read = (filename) => { - try { return fs.readFileSync(`/tmp/check-results/${filename}`, 'utf8').trim(); } - catch { return ''; } - }; - - // Validate PR number — must be a positive integer. - const prNumber = parseInt(read('pr-number.txt'), 10); - if (!Number.isInteger(prNumber) || prNumber <= 0) { - console.log('Invalid or missing PR number in artifact; skipping comment.'); - return; - } - - const exitCode = read('exit-code.txt'); - // Sanitise values that came from external (untrusted) sources. - const headRef = read('head-ref.txt').replace(/[^a-zA-Z0-9._\/-]/g, ''); - - const marker = ''; - const runUrl = `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.payload.workflow_run.id}`; - - const { data: comments } = await github.rest.issues.listComments({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: prNumber, - }); - const existing = comments.find(c => c.body.includes(marker)); - - // --------------------------------------------------------------- - // All templates passed (or no templates changed) — clean up. - // --------------------------------------------------------------- - if (exitCode === '0') { - if (existing) { - await github.rest.issues.deleteComment({ - owner: context.repo.owner, - repo: context.repo.repo, - comment_id: existing.id, - }); - console.log('Deleted stale failure comment.'); - } - return; - } - - // --------------------------------------------------------------- - // Build the failure comment. - // --------------------------------------------------------------- - - // Parse structured results if available; fall back to raw output. - let tableRows = ''; - let summaryLine = ''; - const rawOutput = read('output.txt'); - - try { - const resultsJson = read('results.json'); - if (resultsJson) { - const data = JSON.parse(resultsJson); - summaryLine = `**${data.failed} of ${data.total} template(s) failed.**`; - - // Build a markdown table row for each failed template. - // SDK Range shows what package.json / go.mod specifies. - // Resolved shows what was actually installed/used — so a - // '^1.5.0' range resolving to '2.0.0' is immediately visible. - const failedTemplates = data.templates.filter(t => t.status !== 'pass'); - const rows = failedTemplates.map(t => { - // Sanitise all values read from the artifact. - const sanitise = s => String(s || '').replace(/[|`]/g, ' ').slice(0, 80); - const name = sanitise(t.name); - const lang = sanitise(t.language); - const range = sanitise(t.sdk_range); - const resolved = sanitise(t.sdk_resolved); - const step = sanitise(t.failure_step); - // Show a short excerpt of the first real error line - const output = (t.failure_output || '') - .split('\n') - .map(l => l.trim()) - .filter(l => l && !l.startsWith('npm warn') && !l.startsWith('>')) - .slice(0, 3) - .join(' · ') - .replace(/[|`]/g, ' ') - .slice(0, 120); - return `| \`${name}\` | ${lang} | \`${range}\` | \`${resolved}\` | ${step} | ${output} |`; - }); - - if (rows.length > 0) { - tableRows = [ - '| Template | Language | SDK Range | Resolved | Step | Error |', - '|----------|----------|-----------|----------|------|-------|', - ...rows, - ].join('\n'); - } - } - } catch (e) { - console.log('Could not parse results.json, falling back to raw output:', e.message); - } - - // If we couldn't build a table, fall back to a plain code block. - let failureSection = ''; - if (tableRows) { - failureSection = tableRows; - } else { - const resultsMatch = rawOutput.match(/={8,}\nResults:.*\n={8,}[\s\S]*/); - const excerpt = resultsMatch ? resultsMatch[0].trim() : rawOutput.trim(); - failureSection = '```\n' + excerpt.slice(0, 3000) + '\n```'; - } - - const body = [ - '## ⚠️ Template Check Failures', - '', - summaryLine || 'One or more templates changed in this PR failed validation or compilation.', - '', - '> **SDK Range** is what `package.json` / `go.mod` specifies.', - '> **Resolved** is the exact version that was installed and used.', - '> A range like `^1.5.0` resolving to `2.0.0` indicates a breaking SDK release.', - '', - failureSection, - '', - `[View full output →](${runUrl})`, - '', - '
', - 'What should I do?', - '', - '- **Fix the template:** Update the template so it compiles against the SDK version it specifies.', - '- **Pin the SDK version:** Change the range (e.g. `^1.5.0`) to an exact version (e.g. `1.5.2`) if you want to lock against a known-good release.', - '', - '
', - ].join('\n'); - - const commentBody = `${marker}\n${body}`; - - if (existing) { - await github.rest.issues.updateComment({ - owner: context.repo.owner, - repo: context.repo.repo, - comment_id: existing.id, - body: commentBody, - }); - console.log('Updated existing failure comment.'); - } else { - await github.rest.issues.createComment({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: prNumber, - body: commentBody, - }); - console.log('Posted new failure comment.'); - } + const path = require('path'); + const run = require(path.join(process.env.GITHUB_WORKSPACE, 'scripts', 'template-check-comment.js')); + await run({ github, context }); diff --git a/.github/workflows/template-check.yml b/.github/workflows/template-check.yml index 64614096..67f6fffe 100644 --- a/.github/workflows/template-check.yml +++ b/.github/workflows/template-check.yml @@ -4,6 +4,10 @@ on: pull_request: types: [opened, synchronize, reopened] +concurrency: + group: template-compat-${{ github.head_ref || github.ref }} + cancel-in-progress: true + # Non-blocking informational check — never gates merges. # Runs only against templates changed in the PR. # Failures are reported via a PR comment (see template-check-comment.yml). diff --git a/scripts/check-templates.sh b/scripts/check-templates.sh index b8cd5940..457d5060 100755 --- a/scripts/check-templates.sh +++ b/scripts/check-templates.sh @@ -61,7 +61,7 @@ REPO_ROOT="$(dirname "$SCRIPT_DIR")" RESULTS_FILE="${RESULTS_FILE:-/tmp/template-results.json}" JSONL_FILE=$(mktemp /tmp/cre-check-XXXXXX.jsonl) -# Lockfile tracking — populated by check_ts_workflow before each npm install. +# Lockfile tracking — populated by check_ts_workflow before each bun install. # Backed-up lockfiles are restored; generated ones are deleted on exit. LOCKFILE_BACKUPS=() # "backup_path:original_path" pairs GENERATED_LOCKFILES=() # paths to delete on exit @@ -287,42 +287,45 @@ process.stdout.write(deps['@chainlink/cre-sdk'] || 'unknown'); vlog " ✅ workflow.yaml valid" fi - # 2. npm install — track package-lock.json so we can restore it on exit. + # 2. bun install — track lockfiles so we can restore them on exit. # Use git to distinguish committed lockfiles (restore) from untracked/absent # ones (delete). This correctly handles leftovers from previous script runs. local _wf_rel="${abs_wf#"$REPO_ROOT/"}" - if git -C "$REPO_ROOT" ls-files --error-unmatch "$_wf_rel/package-lock.json" &>/dev/null; then - # File is committed to git — back up its current content to restore later - cp package-lock.json package-lock.json.__cre_bak - LOCKFILE_BACKUPS+=("$(pwd)/package-lock.json.__cre_bak:$(pwd)/package-lock.json") - else - # File is not tracked — mark for deletion after the check - GENERATED_LOCKFILES+=("$(pwd)/package-lock.json") - fi + local lockfile + for lockfile in package-lock.json bun.lock; do + if git -C "$REPO_ROOT" ls-files --error-unmatch "$_wf_rel/$lockfile" &>/dev/null; then + cp "$lockfile" "${lockfile}.__cre_bak" + LOCKFILE_BACKUPS+=("$(pwd)/${lockfile}.__cre_bak:$(pwd)/$lockfile") + else + GENERATED_LOCKFILES+=("$(pwd)/$lockfile") + fi + done vlog " Installing dependencies (SDK range: $sdk_range)..." - if ! run_captured _out npm install --no-audit --fund=false; then + if ! run_captured _out bun install; then printf '%s' "$_out" > "$_FAIL_OUT" - info " ❌ npm install failed" - record_fail "$display" "typescript" "$sdk_range" "$sdk_resolved" "npm install" "$_FAIL_OUT" + info " ❌ bun install failed" + record_fail "$display" "typescript" "$sdk_range" "$sdk_resolved" "bun install" "$_FAIL_OUT" return 0 fi - # 2b. npm install in template contracts/ when present (generated bindings import viem / cre-sdk). + # 2b. bun install in template contracts/ when present (generated bindings import viem / cre-sdk). local contracts_dir="$REPO_ROOT/$template_dir/contracts" if [[ -f "$contracts_dir/package.json" ]]; then vlog " Installing contracts dependencies..." cd "$contracts_dir" local _contracts_rel="${contracts_dir#"$REPO_ROOT/"}" - if git -C "$REPO_ROOT" ls-files --error-unmatch "$_contracts_rel/package-lock.json" &>/dev/null; then - cp package-lock.json package-lock.json.__cre_bak - LOCKFILE_BACKUPS+=("$(pwd)/package-lock.json.__cre_bak:$(pwd)/package-lock.json") - else - GENERATED_LOCKFILES+=("$(pwd)/package-lock.json") - fi - if ! run_captured _out npm install --no-audit --fund=false; then + for lockfile in package-lock.json bun.lock; do + if git -C "$REPO_ROOT" ls-files --error-unmatch "$_contracts_rel/$lockfile" &>/dev/null; then + cp "$lockfile" "${lockfile}.__cre_bak" + LOCKFILE_BACKUPS+=("$(pwd)/${lockfile}.__cre_bak:$(pwd)/$lockfile") + else + GENERATED_LOCKFILES+=("$(pwd)/$lockfile") + fi + done + if ! run_captured _out bun install; then printf '%s' "$_out" > "$_FAIL_OUT" - info " ❌ npm install (contracts) failed" - record_fail "$display" "typescript" "$sdk_range" "$sdk_resolved" "npm install (contracts)" "$_FAIL_OUT" + info " ❌ bun install (contracts) failed" + record_fail "$display" "typescript" "$sdk_range" "$sdk_resolved" "bun install (contracts)" "$_FAIL_OUT" cd "$abs_wf" return 0 fi @@ -344,7 +347,7 @@ process.stdout.write(deps['@chainlink/cre-sdk'] || 'unknown'); 2>/dev/null || echo "no") if [[ "$has_typecheck" == "yes" ]]; then vlog " Running typecheck..." - if ! run_captured _out npm run typecheck --silent; then + if ! run_captured _out bun run typecheck; then printf '%s' "$_out" > "$_FAIL_OUT" info " ❌ typecheck failed (SDK $sdk_resolved)" record_fail "$display" "typescript" "$sdk_range" "$sdk_resolved" "typecheck" "$_FAIL_OUT" @@ -356,17 +359,9 @@ process.stdout.write(deps['@chainlink/cre-sdk'] || 'unknown'); fi # 5. cre-compile (if main.ts exists) - # Prefer `bun x` so the Bun runtime is used explicitly (cre-compile's bin uses a bun shebang; - # `npx` on some Linux/npm combinations does not invoke it reliably). if [[ -f "main.ts" ]]; then vlog " Running cre-compile..." - local _compile_cmd - if command -v bun >/dev/null 2>&1; then - _compile_cmd=(bun x cre-compile main.ts) - else - _compile_cmd=(npx --no cre-compile main.ts) - fi - if ! run_captured _out "${_compile_cmd[@]}"; then + if ! run_captured _out bunx cre-compile main.ts; then printf '%s' "$_out" > "$_FAIL_OUT" info " ❌ cre-compile failed (SDK $sdk_resolved)" record_fail "$display" "typescript" "$sdk_range" "$sdk_resolved" "cre-compile" "$_FAIL_OUT" diff --git a/scripts/template-check-comment.js b/scripts/template-check-comment.js new file mode 100644 index 00000000..7179ca6a --- /dev/null +++ b/scripts/template-check-comment.js @@ -0,0 +1,154 @@ +/** + * Used by .github/workflows/template-check-comment.yml. + * Reads /tmp/check-results from the prior workflow's artifact and posts or + * removes a PR comment via the GitHub API. + */ +module.exports = async ({ github, context }) => { + const fs = require('fs'); + + const read = (filename) => { + try { + return fs.readFileSync(`/tmp/check-results/${filename}`, 'utf8').trim(); + } catch { + return ''; + } + }; + + // Validate PR number — must be a positive integer. + const prNumber = parseInt(read('pr-number.txt'), 10); + if (!Number.isInteger(prNumber) || prNumber <= 0) { + console.log('Invalid or missing PR number in artifact; skipping comment.'); + return; + } + + const exitCode = read('exit-code.txt'); + + const marker = ''; + const runUrl = `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.payload.workflow_run.id}`; + + const { data: comments } = await github.rest.issues.listComments({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: prNumber, + }); + const existing = comments.find((c) => c.body.includes(marker)); + + // --------------------------------------------------------------- + // All templates passed (or no templates changed) — clean up. + // --------------------------------------------------------------- + if (exitCode === '0') { + if (existing) { + await github.rest.issues.deleteComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: existing.id, + }); + console.log('Deleted stale failure comment.'); + } + return; + } + + // --------------------------------------------------------------- + // Build the failure comment. + // --------------------------------------------------------------- + + // Parse structured results if available; fall back to raw output. + let tableRows = ''; + let summaryLine = ''; + const rawOutput = read('output.txt'); + + try { + const resultsJson = read('results.json'); + if (resultsJson) { + const data = JSON.parse(resultsJson); + summaryLine = `**${data.failed} of ${data.total} template(s) failed.**`; + + // Build a markdown table row for each failed template. + // SDK Range shows what package.json / go.mod specifies. + // Resolved shows what was actually installed/used — so a + // '^1.5.0' range resolving to '2.0.0' is immediately visible. + const failedTemplates = data.templates.filter((t) => t.status !== 'pass'); + const rows = failedTemplates.map((t) => { + // Sanitise all values read from the artifact. + const sanitise = (s) => String(s || '').replace(/[|`]/g, ' ').slice(0, 80); + const name = sanitise(t.name); + const lang = sanitise(t.language); + const range = sanitise(t.sdk_range); + const resolved = sanitise(t.sdk_resolved); + const step = sanitise(t.failure_step); + // Show a short excerpt of the first real error line + const output = (t.failure_output || '') + .split('\n') + .map((l) => l.trim()) + .filter((l) => l && !l.startsWith('npm warn') && !l.startsWith('>')) + .slice(0, 3) + .join(' · ') + .replace(/[|`]/g, ' ') + .slice(0, 120); + return `| \`${name}\` | ${lang} | \`${range}\` | \`${resolved}\` | ${step} | ${output} |`; + }); + + if (rows.length > 0) { + tableRows = [ + '| Template | Language | SDK Range | Resolved | Step | Error |', + '|----------|----------|-----------|----------|------|-------|', + ...rows, + ].join('\n'); + } + } + } catch (e) { + console.log('Could not parse results.json, falling back to raw output:', e.message); + } + + // If we couldn't build a table, fall back to a plain code block. + let failureSection = ''; + if (tableRows) { + failureSection = tableRows; + } else { + const resultsMatch = rawOutput.match(/={8,}\nResults:.*\n={8,}[\s\S]*/); + const excerpt = resultsMatch ? resultsMatch[0].trim() : rawOutput.trim(); + failureSection = '```\n' + excerpt.slice(0, 3000) + '\n```'; + } + + const body = [ + '## ⚠️ Template Check Failures', + '', + summaryLine || 'One or more templates changed in this PR failed validation or compilation.', + '', + '> **SDK Range** is what `package.json` / `go.mod` specifies.', + '> **Resolved** is the exact version that was installed and used.', + '> A range like `^1.5.0` resolving to `2.0.0` indicates a breaking SDK release.', + '', + failureSection, + '', + `[View full output →](${runUrl})`, + '', + '
', + 'What should I do?', + '', + '- **Fix the template:** Update the template so it compiles against the SDK version it specifies.', + '- **Pin the SDK version:** Change the range (e.g. `^1.5.0`) to an exact version (e.g. `1.5.2`) if you want to lock against a known-good release.', + '', + '
', + ].join('\n'); + + const commentBody = `${marker}\n${body}`; + + if (existing) { + await github.rest.issues.updateComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: existing.id, + body: commentBody, + }); + console.log('Updated existing failure comment.'); + } else { + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: prNumber, + body: commentBody, + }); + console.log('Posted new failure comment.'); + } +}; From 6d75973a6cf7aadc80c0b2afbd535ac739730143 Mon Sep 17 00:00:00 2001 From: Russell Stern Date: Mon, 4 May 2026 11:25:38 -0400 Subject: [PATCH 6/8] Switched to using major tags --- .github/workflows/template-check-comment.yml | 6 +++--- .github/workflows/template-check.yml | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/template-check-comment.yml b/.github/workflows/template-check-comment.yml index f4e1649a..e564c0d0 100644 --- a/.github/workflows/template-check-comment.yml +++ b/.github/workflows/template-check-comment.yml @@ -25,10 +25,10 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + uses: actions/checkout@v6 - name: Download results artifact - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 + uses: actions/download-artifact@v4 with: name: template-check-results path: /tmp/check-results @@ -36,7 +36,7 @@ jobs: github-token: ${{ secrets.GITHUB_TOKEN }} - name: Post or remove PR comment - uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 + uses: actions/github-script@v8 with: script: | const path = require('path'); diff --git a/.github/workflows/template-check.yml b/.github/workflows/template-check.yml index 67f6fffe..c69a1724 100644 --- a/.github/workflows/template-check.yml +++ b/.github/workflows/template-check.yml @@ -32,7 +32,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + uses: actions/checkout@v6 with: fetch-depth: 0 # full history needed for git diff against base branch @@ -40,7 +40,7 @@ jobs: # GOTOOLCHAIN=auto lets Go download the exact toolchain declared in go.mod # (e.g. "toolchain go1.24.10") if it differs from the installed version. - name: Setup Go - uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5.6.0 + uses: actions/setup-go@v5 with: go-version: 'stable' cache: false # cache managed explicitly below @@ -49,7 +49,7 @@ jobs: # Node + npm are required for TypeScript template checks. - name: Setup Node - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 + uses: actions/setup-node@v4 with: node-version: '22' @@ -61,7 +61,7 @@ jobs: # Cache Go module downloads. Keyed on all go.sum files so any new module # version busts the cache while unchanged templates stay fast. - name: Cache Go modules - uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4 + uses: actions/cache@v5 with: path: | ~/go/pkg/mod @@ -73,7 +73,7 @@ jobs: # @chainlink/cre-sdk version (e.g. ^1.5.0) will hit the cache after # the first install, avoiding repeated network downloads. - name: Cache npm - uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4 + uses: actions/cache@v5 with: path: ~/.npm key: ${{ runner.os }}-npm-templates-${{ hashFiles('**/package.json') }} @@ -116,7 +116,7 @@ jobs: - name: Upload results artifact if: always() - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + uses: actions/upload-artifact@v4 with: name: template-check-results path: /tmp/check-results/ From 4662b5839f030fa0c12169decc61aa138d0f9fba Mon Sep 17 00:00:00 2001 From: Russell Stern Date: Mon, 4 May 2026 12:29:37 -0400 Subject: [PATCH 7/8] Updated action versions --- .github/workflows/template-check-comment.yml | 2 +- .github/workflows/template-check.yml | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/template-check-comment.yml b/.github/workflows/template-check-comment.yml index e564c0d0..588dc003 100644 --- a/.github/workflows/template-check-comment.yml +++ b/.github/workflows/template-check-comment.yml @@ -28,7 +28,7 @@ jobs: uses: actions/checkout@v6 - name: Download results artifact - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v8 with: name: template-check-results path: /tmp/check-results diff --git a/.github/workflows/template-check.yml b/.github/workflows/template-check.yml index c69a1724..11817388 100644 --- a/.github/workflows/template-check.yml +++ b/.github/workflows/template-check.yml @@ -49,9 +49,9 @@ jobs: # Node + npm are required for TypeScript template checks. - name: Setup Node - uses: actions/setup-node@v4 + uses: actions/setup-node@v6 with: - node-version: '22' + node-version: '24' # cre-compile is shipped with a bun shebang; npx invokes it as /usr/bin/env bun. # Match local dev machines where Bun is installed alongside Node. @@ -116,7 +116,7 @@ jobs: - name: Upload results artifact if: always() - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: template-check-results path: /tmp/check-results/ From b1956e34c9a5165a6257ac4dd07f9bd45a5fa112 Mon Sep 17 00:00:00 2001 From: Russell Stern Date: Mon, 18 May 2026 14:54:50 -0400 Subject: [PATCH 8/8] Make template checks a required github action --- .github/workflows/template-check-comment.yml | 44 ------ .github/workflows/template-check.yml | 59 +------ scripts/template-check-comment.js | 154 ------------------- 3 files changed, 4 insertions(+), 253 deletions(-) delete mode 100644 .github/workflows/template-check-comment.yml delete mode 100644 scripts/template-check-comment.js diff --git a/.github/workflows/template-check-comment.yml b/.github/workflows/template-check-comment.yml deleted file mode 100644 index 588dc003..00000000 --- a/.github/workflows/template-check-comment.yml +++ /dev/null @@ -1,44 +0,0 @@ -name: Template Check Comment - -# Triggered when the unprivileged "Template Check" workflow completes. -# This workflow has pull-requests: write so it can post PR comments. -# It checks out the default branch (only) to load scripts/template-check-comment.js, -# then reads the artifact produced by the check workflow. -# -# SECURITY: workflow_run always runs on the default branch, so this workflow -# definition and checked-out script cannot be tampered with by a PR contributor. -# Artifact contents are treated as untrusted strings and sanitised before use. - -on: - workflow_run: - workflows: ["Template Check"] - types: [completed] - -permissions: - contents: read - pull-requests: write - actions: read # required to download artifacts from another workflow run - -jobs: - post-comment: - runs-on: ubuntu-latest - - steps: - - name: Checkout repository - uses: actions/checkout@v6 - - - name: Download results artifact - uses: actions/download-artifact@v8 - with: - name: template-check-results - path: /tmp/check-results - run-id: ${{ github.event.workflow_run.id }} - github-token: ${{ secrets.GITHUB_TOKEN }} - - - name: Post or remove PR comment - uses: actions/github-script@v8 - with: - script: | - const path = require('path'); - const run = require(path.join(process.env.GITHUB_WORKSPACE, 'scripts', 'template-check-comment.js')); - await run({ github, context }); diff --git a/.github/workflows/template-check.yml b/.github/workflows/template-check.yml index 11817388..d4f5ec73 100644 --- a/.github/workflows/template-check.yml +++ b/.github/workflows/template-check.yml @@ -8,16 +8,14 @@ concurrency: group: template-compat-${{ github.head_ref || github.ref }} cancel-in-progress: true -# Non-blocking informational check — never gates merges. -# Runs only against templates changed in the PR. -# Failures are reported via a PR comment (see template-check-comment.yml). +# Blocking merge gate for templates changed in the PR. # # Checks performed per template: -# TypeScript: YAML validation, npm install, typecheck, cre-compile to WASM +# TypeScript: YAML validation, bun install, typecheck, cre-compile to WASM # Go: YAML validation, go mod verify, go vet, go build (GOOS=wasip1 GOARCH=wasm) # # The SDK version used is captured from the installed package (TS) or go.mod (Go) -# and included in the PR comment so range-vs-resolved mismatches are visible. +# and printed in the Actions log when a check fails. permissions: contents: read @@ -80,57 +78,8 @@ jobs: restore-keys: ${{ runner.os }}-npm-templates- - name: Run template checks - id: template-check env: BASE_REF: ${{ github.base_ref }} RESULTS_FILE: /tmp/template-results.json GOTOOLCHAIN: auto - run: | - set +e - OUTPUT=$(./scripts/check-templates.sh 2>&1) - EXIT_CODE=$? - set -e - - echo "$OUTPUT" > /tmp/template-check-output.txt - - # Surface output in the Actions log regardless of pass/fail - echo "$OUTPUT" - - echo "exit_code=$EXIT_CODE" >> "$GITHUB_OUTPUT" - - - name: Save results for comment workflow - if: always() - env: - PR_NUMBER: ${{ github.event.pull_request.number }} - HEAD_REF: ${{ github.head_ref }} - EXIT_CODE: ${{ steps.template-check.outputs.exit_code || '0' }} - run: | - mkdir -p /tmp/check-results - printf '%s' "$PR_NUMBER" > /tmp/check-results/pr-number.txt - printf '%s' "$HEAD_REF" > /tmp/check-results/head-ref.txt - printf '%s' "$EXIT_CODE" > /tmp/check-results/exit-code.txt - [[ -f /tmp/template-check-output.txt ]] \ - && cp /tmp/template-check-output.txt /tmp/check-results/output.txt || true - [[ -f /tmp/template-results.json ]] \ - && cp /tmp/template-results.json /tmp/check-results/results.json || true - - - name: Upload results artifact - if: always() - uses: actions/upload-artifact@v7 - with: - name: template-check-results - path: /tmp/check-results/ - retention-days: 7 - - # Always exit 0 — this job is informational, not a merge gate. - - name: Report result (non-blocking) - if: always() - run: | - EXIT_CODE="${{ steps.template-check.outputs.exit_code || '0' }}" - if [[ "$EXIT_CODE" == "0" ]]; then - echo "✅ All changed templates passed." - else - echo "⚠️ Some templates failed — see PR comment for details." - echo "This check is informational and does not block merging." - fi - exit 0 + run: ./scripts/check-templates.sh diff --git a/scripts/template-check-comment.js b/scripts/template-check-comment.js deleted file mode 100644 index 7179ca6a..00000000 --- a/scripts/template-check-comment.js +++ /dev/null @@ -1,154 +0,0 @@ -/** - * Used by .github/workflows/template-check-comment.yml. - * Reads /tmp/check-results from the prior workflow's artifact and posts or - * removes a PR comment via the GitHub API. - */ -module.exports = async ({ github, context }) => { - const fs = require('fs'); - - const read = (filename) => { - try { - return fs.readFileSync(`/tmp/check-results/${filename}`, 'utf8').trim(); - } catch { - return ''; - } - }; - - // Validate PR number — must be a positive integer. - const prNumber = parseInt(read('pr-number.txt'), 10); - if (!Number.isInteger(prNumber) || prNumber <= 0) { - console.log('Invalid or missing PR number in artifact; skipping comment.'); - return; - } - - const exitCode = read('exit-code.txt'); - - const marker = ''; - const runUrl = `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.payload.workflow_run.id}`; - - const { data: comments } = await github.rest.issues.listComments({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: prNumber, - }); - const existing = comments.find((c) => c.body.includes(marker)); - - // --------------------------------------------------------------- - // All templates passed (or no templates changed) — clean up. - // --------------------------------------------------------------- - if (exitCode === '0') { - if (existing) { - await github.rest.issues.deleteComment({ - owner: context.repo.owner, - repo: context.repo.repo, - comment_id: existing.id, - }); - console.log('Deleted stale failure comment.'); - } - return; - } - - // --------------------------------------------------------------- - // Build the failure comment. - // --------------------------------------------------------------- - - // Parse structured results if available; fall back to raw output. - let tableRows = ''; - let summaryLine = ''; - const rawOutput = read('output.txt'); - - try { - const resultsJson = read('results.json'); - if (resultsJson) { - const data = JSON.parse(resultsJson); - summaryLine = `**${data.failed} of ${data.total} template(s) failed.**`; - - // Build a markdown table row for each failed template. - // SDK Range shows what package.json / go.mod specifies. - // Resolved shows what was actually installed/used — so a - // '^1.5.0' range resolving to '2.0.0' is immediately visible. - const failedTemplates = data.templates.filter((t) => t.status !== 'pass'); - const rows = failedTemplates.map((t) => { - // Sanitise all values read from the artifact. - const sanitise = (s) => String(s || '').replace(/[|`]/g, ' ').slice(0, 80); - const name = sanitise(t.name); - const lang = sanitise(t.language); - const range = sanitise(t.sdk_range); - const resolved = sanitise(t.sdk_resolved); - const step = sanitise(t.failure_step); - // Show a short excerpt of the first real error line - const output = (t.failure_output || '') - .split('\n') - .map((l) => l.trim()) - .filter((l) => l && !l.startsWith('npm warn') && !l.startsWith('>')) - .slice(0, 3) - .join(' · ') - .replace(/[|`]/g, ' ') - .slice(0, 120); - return `| \`${name}\` | ${lang} | \`${range}\` | \`${resolved}\` | ${step} | ${output} |`; - }); - - if (rows.length > 0) { - tableRows = [ - '| Template | Language | SDK Range | Resolved | Step | Error |', - '|----------|----------|-----------|----------|------|-------|', - ...rows, - ].join('\n'); - } - } - } catch (e) { - console.log('Could not parse results.json, falling back to raw output:', e.message); - } - - // If we couldn't build a table, fall back to a plain code block. - let failureSection = ''; - if (tableRows) { - failureSection = tableRows; - } else { - const resultsMatch = rawOutput.match(/={8,}\nResults:.*\n={8,}[\s\S]*/); - const excerpt = resultsMatch ? resultsMatch[0].trim() : rawOutput.trim(); - failureSection = '```\n' + excerpt.slice(0, 3000) + '\n```'; - } - - const body = [ - '## ⚠️ Template Check Failures', - '', - summaryLine || 'One or more templates changed in this PR failed validation or compilation.', - '', - '> **SDK Range** is what `package.json` / `go.mod` specifies.', - '> **Resolved** is the exact version that was installed and used.', - '> A range like `^1.5.0` resolving to `2.0.0` indicates a breaking SDK release.', - '', - failureSection, - '', - `[View full output →](${runUrl})`, - '', - '
', - 'What should I do?', - '', - '- **Fix the template:** Update the template so it compiles against the SDK version it specifies.', - '- **Pin the SDK version:** Change the range (e.g. `^1.5.0`) to an exact version (e.g. `1.5.2`) if you want to lock against a known-good release.', - '', - '
', - ].join('\n'); - - const commentBody = `${marker}\n${body}`; - - if (existing) { - await github.rest.issues.updateComment({ - owner: context.repo.owner, - repo: context.repo.repo, - comment_id: existing.id, - body: commentBody, - }); - console.log('Updated existing failure comment.'); - } else { - await github.rest.issues.createComment({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: prNumber, - body: commentBody, - }); - console.log('Posted new failure comment.'); - } -};