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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 76 additions & 2 deletions .github/workflows/upstream-sync.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ jobs:
uses: actions/checkout@v4
with:
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}
# PAT required to push workflow file changes (GITHUB_TOKEN lacks 'workflows' permission)
token: ${{ secrets.PAT_NORI_CLI_WORKFLOW_SYNC }}

- name: Configure git
run: |
Expand Down Expand Up @@ -122,6 +123,7 @@ jobs:

- name: Create sync branch
if: steps.check.outputs.exists == 'false'
id: sync
run: |
set -euo pipefail

Expand All @@ -133,17 +135,70 @@ jobs:

if [[ "$dry_run" == "true" ]]; then
echo "::notice::DRY RUN: Would create branch $sync_branch from $target_tag"
echo "sanitized_files=" >> "$GITHUB_OUTPUT"
else
git checkout -b "$sync_branch" "$target_tag"

# Sanitize upstream workflow triggers to prevent unwanted runs
sanitized_files=""
for workflow in .github/workflows/*.yml .github/workflows/*.yaml; do
[ -f "$workflow" ] || continue

# Skip our fork-specific workflows
case "$(basename "$workflow")" in
upstream-sync.yml|rust-ci.yml) continue ;;
esac

echo "Sanitizing: $workflow"

# Replace on: block with workflow_dispatch only using awk
awk '
/^on:/ {
in_on = 1
print "on: workflow_dispatch"
next
}
in_on && /^$/ {
in_on = 0
print
next
}
in_on && /^[^ \t#]/ {
in_on = 0
}
in_on && /^[ \t]/ {
next
}
in_on && /^#/ {
next
}
!in_on { print }
' "$workflow" > "${workflow}.tmp" && mv "${workflow}.tmp" "$workflow"

sanitized_files="$sanitized_files- \`$(basename "$workflow")\`\n"
done

if [[ -n "$sanitized_files" ]]; then
git add .github/workflows/
git commit -m "chore: sanitize upstream workflow triggers for fork safety"
echo "sanitized_files<<EOF" >> "$GITHUB_OUTPUT"
echo -e "$sanitized_files" >> "$GITHUB_OUTPUT"
echo "EOF" >> "$GITHUB_OUTPUT"
else
echo "::notice::No upstream workflows to sanitize"
echo "sanitized_files=" >> "$GITHUB_OUTPUT"
fi

git push origin "$sync_branch"
fi

- name: Create draft PR
if: steps.check.outputs.exists == 'false'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_TOKEN: ${{ secrets.PAT_NORI_CLI_WORKFLOW_SYNC }}
TARGET_TAG: ${{ steps.tag.outputs.target_tag }}
SYNC_BRANCH: ${{ steps.tag.outputs.sync_branch }}
SANITIZED_FILES: ${{ steps.sync.outputs.sanitized_files }}
DRY_RUN: ${{ inputs.dry_run }}
run: |
set -euo pipefail
Expand All @@ -153,6 +208,16 @@ jobs:

pr_title="Sync upstream $TARGET_TAG"

# Build sanitized workflows section
if [[ -n "$SANITIZED_FILES" ]]; then
sanitized_section="### Workflow Sanitization

The following upstream workflows had their triggers replaced with \\\`workflow_dispatch\\\`:
$SANITIZED_FILES"
else
sanitized_section=""
fi

# Build PR body using heredoc
pr_body=$(cat <<EOF
## Upstream Sync
Expand All @@ -165,6 +230,8 @@ jobs:
- **Commits to merge:** ~$commit_count
- **Release notes:** [GitHub Release](https://github.com/openai/codex/releases/tag/$TARGET_TAG)

$sanitized_section

### Merge Instructions

1. Review the changes for conflicts with our ACP fork work
Expand Down Expand Up @@ -201,10 +268,17 @@ jobs:
fi

- name: Summary
env:
SANITIZED_FILES: ${{ steps.sync.outputs.sanitized_files }}
run: |
echo "## Upstream Sync Summary" >> "$GITHUB_STEP_SUMMARY"
echo "" >> "$GITHUB_STEP_SUMMARY"
echo "- **Target tag:** ${{ steps.tag.outputs.target_tag }}" >> "$GITHUB_STEP_SUMMARY"
echo "- **Sync branch:** ${{ steps.tag.outputs.sync_branch }}" >> "$GITHUB_STEP_SUMMARY"
echo "- **Branch existed:** ${{ steps.check.outputs.exists }}" >> "$GITHUB_STEP_SUMMARY"
echo "- **Dry run:** ${{ inputs.dry_run || 'false' }}" >> "$GITHUB_STEP_SUMMARY"
if [[ -n "$SANITIZED_FILES" ]]; then
echo "" >> "$GITHUB_STEP_SUMMARY"
echo "### Sanitized Workflows" >> "$GITHUB_STEP_SUMMARY"
echo "$SANITIZED_FILES" >> "$GITHUB_STEP_SUMMARY"
fi
10 changes: 10 additions & 0 deletions scripts/test-sanitize/fixtures/expected-comments.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# This workflow runs on push
name: With Comments

on: workflow_dispatch

jobs:
test:
runs-on: ubuntu-latest
steps:
- run: echo "test"
12 changes: 12 additions & 0 deletions scripts/test-sanitize/fixtures/expected-complex.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
name: Complex Workflow

on: workflow_dispatch

env:
NODE_VERSION: '20'

jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
9 changes: 9 additions & 0 deletions scripts/test-sanitize/fixtures/expected-multiline.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
name: CI

on: workflow_dispatch

jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
9 changes: 9 additions & 0 deletions scripts/test-sanitize/fixtures/expected-singleline.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
name: Simple

on: workflow_dispatch

jobs:
test:
runs-on: ubuntu-latest
steps:
- run: echo "hello"
15 changes: 15 additions & 0 deletions scripts/test-sanitize/fixtures/input-comments.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# This workflow runs on push
name: With Comments

on:
# Run on push to main
push:
branches: [main]
# Also on PRs
pull_request:

jobs:
test:
runs-on: ubuntu-latest
steps:
- run: echo "test"
29 changes: 29 additions & 0 deletions scripts/test-sanitize/fixtures/input-complex.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
name: Complex Workflow

on:
push:
branches:
- main
- 'release/**'
paths:
- 'src/**'
- '!src/**/*.md'
pull_request:
types: [opened, synchronize, reopened]
schedule:
- cron: '0 0 * * *'
workflow_dispatch:
inputs:
debug:
description: 'Enable debug mode'
required: false
type: boolean

env:
NODE_VERSION: '20'

jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
13 changes: 13 additions & 0 deletions scripts/test-sanitize/fixtures/input-multiline.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
name: CI

on:
push:
branches: [main]
pull_request:
branches: [main]

jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
9 changes: 9 additions & 0 deletions scripts/test-sanitize/fixtures/input-singleline.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
name: Simple

on: push

jobs:
test:
runs-on: ubuntu-latest
steps:
- run: echo "hello"
112 changes: 112 additions & 0 deletions scripts/test-sanitize/test-sanitize.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
#!/usr/bin/env bash
# Test script for workflow sanitization logic
# RED phase: This test will fail until we implement the sanitize function

set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
TEST_DIR="$SCRIPT_DIR/fixtures"
TEMP_DIR="$(mktemp -d)"

trap "rm -rf $TEMP_DIR" EXIT

# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
NC='\033[0m' # No Color

failed=0
passed=0

# The sanitize function
sanitize_workflow() {
local input_file="$1"
local output_file="$2"

# Replace on: block with workflow_dispatch only using awk
# Logic:
# 1. When we see ^on: (at column 0), enter "in_on" mode, print replacement
# 2. While in_on, skip lines starting with whitespace (indented on: content)
# 3. When we see a non-whitespace line at column 0, exit in_on mode
# 4. Print all other lines normally
awk '
/^on:/ {
in_on = 1
print "on: workflow_dispatch"
next
}
in_on && /^$/ {
# Empty line ends the on: block
in_on = 0
print
next
}
in_on && /^[^ \t#]/ {
# Non-indented line ends the on: block
in_on = 0
}
in_on && /^[ \t]/ {
# Indented content - skip
next
}
in_on && /^#/ {
# Comment inside on: block - skip
next
}
!in_on { print }
' "$input_file" > "$output_file"
}

# Test helper
assert_output() {
local test_name="$1"
local input_file="$2"
local expected_file="$3"

local actual_file="$TEMP_DIR/actual.yml"
sanitize_workflow "$input_file" "$actual_file"

if diff -q "$expected_file" "$actual_file" > /dev/null 2>&1; then
echo -e "${GREEN}✓ PASS${NC}: $test_name"
passed=$((passed + 1))
else
echo -e "${RED}✗ FAIL${NC}: $test_name"
echo " Expected:"
sed 's/^/ /' "$expected_file"
echo " Actual:"
sed 's/^/ /' "$actual_file"
echo " Diff:"
diff "$expected_file" "$actual_file" | sed 's/^/ /' || true
failed=$((failed + 1))
fi
}

echo "Running workflow sanitization tests..."
echo

# Test 1: Multi-line on: block
assert_output "Multi-line on: block" \
"$TEST_DIR/input-multiline.yml" \
"$TEST_DIR/expected-multiline.yml"

# Test 2: Single-line on: block
assert_output "Single-line on: block" \
"$TEST_DIR/input-singleline.yml" \
"$TEST_DIR/expected-singleline.yml"

# Test 3: Complex on: block with nested structures
assert_output "Complex on: block" \
"$TEST_DIR/input-complex.yml" \
"$TEST_DIR/expected-complex.yml"

# Test 4: on: block with comments
assert_output "on: block with comments" \
"$TEST_DIR/input-comments.yml" \
"$TEST_DIR/expected-comments.yml"

echo
echo "Results: $passed passed, $failed failed"

if [[ $failed -gt 0 ]]; then
exit 1
fi
Loading