Skip to content

Fake update

Fake update #249

name: Generate Students Notebooks branch
on:
push:
branches: [main]
paths: ['**.ipynb']
workflow_dispatch:
defaults:
run:
shell: bash
permissions:
contents: write
actions: write
jobs:
process:
runs-on: ubuntu-latest
env:
CONFIG_PATH: .github/conversion.json
SUMMARY_FILE: /tmp/README.md
steps:
- name: Install Python dependencies
run: pip install chardet
- name: Initialize summary file
run: |
rm -f "$SUMMARY_FILE"
mkdir -p "$(dirname "$SUMMARY_FILE")"
- name: Checkout repo
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Validate JSON config
run: |
# Syntax check with jq, output to summary if error
if ! jq empty "$CONFIG_PATH" >/dev/null 2>&1; then
printf "## ❌ Error in JSON configuration file\n" >> $SUMMARY_FILE
printf "Please fix $CONFIG_PATH error:\n" >> $SUMMARY_FILE
jq empty "$CONFIG_PATH" 2>&1 | sed 's/^jq: //' >> $SUMMARY_FILE
exit 1
fi
# Install Ajv locally (no global install)
npm install ajv@8
# Validate with embedded schema, output errors to summary
node - <<'EOF'
const fs = require('fs');
const Ajv = require('ajv').default;
const summaryFile = process.env.SUMMARY_FILE || '/tmp/README.md';
const configPath = process.env.CONFIG_PATH || '.github/conversion.json';
const schema = {
type: 'object',
required: [
'solution_marker',
'placeholder',
'generate_zip',
'notebooks_dir',
'student_postfix',
'tutor_postfix'
],
properties: {
solution_marker: {
type: 'object',
required: ['code', 'markdown'],
properties: {
code: {type: 'string'},
markdown: {type: 'string'}
},
additionalProperties: false
},
placeholder: {
type: 'object',
required: ['code', 'markdown'],
properties: {
code: {type: 'string'},
markdown: {type: 'string'}
},
additionalProperties: false
},
generate_zip: {type: 'boolean'},
notebooks_dir: {
type: 'array',
items: {type: 'string'},
minItems: 1
},
pre_processing: {type: 'string'},
post_processing: {type: 'string'},
tutor_postfix: {type: 'string'},
student_postfix: {type: 'string'},
rebuild_all: {type: 'boolean'}
}
};
try {
const config = JSON.parse(fs.readFileSync(configPath));
const ajv = new Ajv({allErrors: true, strict: true});
const validate = ajv.compile(schema);
if (!validate(config)) {
const lines = [];
lines.push("## ❌ Error in JSON configuration file");
lines.push("Config validation failed:");
lines.push("```");
validate.errors.forEach(err => {
lines.push(`- ${err.instancePath || '/'} ${err.message}`);
});
lines.push("```");
fs.appendFileSync(summaryFile, lines.join('\n') + '\n');
process.exit(1);
}
} catch (error) {
const lines = [];
lines.push("## ❌ Error in JSON configuration file");
lines.push("Parsing or validation failed:");
lines.push("```");
lines.push(error.message);
lines.push("```");
fs.appendFileSync(summaryFile, lines.join('\n') + '\n');
process.exit(1);
}
EOF
- name: Check monitored directories existence
run: |
EXIT_WITH_ERROR=false
ERROR_HEADER_CREATED=false
while IFS= read -r dir; do
if [ ! -d "$dir" ]; then
if [ "$ERROR_HEADER_CREATED" = false ]; then
printf "## ❌ Error: Missing notebook directories\n" >> $SUMMARY_FILE
printf "The following directories specified in $CONFIG_PATH don't exist:\n" >> $SUMMARY_FILE
ERROR_HEADER_CREATED=true
fi
printf -- "* \`\`\`%s\`\`\`\n" "$dir" >> $SUMMARY_FILE
EXIT_WITH_ERROR=true
fi
done < <(jq -r '.notebooks_dir[]' "$CONFIG_PATH")
if [ "$EXIT_WITH_ERROR" = true ]; then
printf "\nPlease create these directories or update the configuration file.\n" >> $SUMMARY_FILE
exit 1
fi
- name: Store pre and post-processing command for future execution
run: |
echo "PRE_PROCESSING=$(jq -r '.pre_processing // empty' "$CONFIG_PATH")" >> $GITHUB_ENV
echo "POST_PROCESSING=$(jq -r '.post_processing // empty' "$CONFIG_PATH")" >> $GITHUB_ENV
- name: Check pre/post-processing commands syntax
run: |
# Use bash -n to check shell command syntax without execution
if [ -n "$PRE_PROCESSING" ]; then
ERROR_OUTPUT=$(echo "$PRE_PROCESSING" | bash -n 2>&1) || {
printf "## ❌ Error: Pre-processing command syntax error\n" >> $SUMMARY_FILE
printf "Please fix your pre-processing command syntax:\n" >> $SUMMARY_FILE
printf "```
printf "%s\n" "$PRE_PROCESSING" >> $SUMMARY_FILE
printf "```\n\n" >> $SUMMARY_FILE
printf "**Error details:**\n" >> $SUMMARY_FILE
printf "```
printf "%s\n" "$ERROR_OUTPUT" >> $SUMMARY_FILE
printf "```\n" >> $SUMMARY_FILE
exit 1
}
fi
if [ -n "$POST_PROCESSING" ]; then
ERROR_OUTPUT=$(echo "$POST_PROCESSING" | bash -n 2>&1) || {
printf "## ❌ Error: Post-processing command syntax error\n" >> $SUMMARY_FILE
printf "Please fix your post-processing command syntax:\n" >> $SUMMARY_FILE
printf "```
printf "%s\n" "$POST_PROCESSING" >> $SUMMARY_FILE
printf "```\n\n" >> $SUMMARY_FILE
printf "**Error details:**\n" >> $SUMMARY_FILE
printf "```
printf "%s\n" "$ERROR_OUTPUT" >> $SUMMARY_FILE
printf "```\n" >> $SUMMARY_FILE
exit 1
}
fi
- name: Setup Python
uses: actions/setup-python@v4
with:
python-version: '3.12'
- name: Setup uv
uses: astral-sh/setup-uv@v6
with:
enable-cache: true
cache-dependency-glob: ".github/scripts/student_version.py"
- name: Execute pre-processing command
run: |
OUTPUT=$(bash -c "$PRE_PROCESSING" 2>&1)
EXIT_CODE=$?
if [ $EXIT_CODE -eq 0 ]; then
printf "## Pre-processing executed successfully ✅\n" >> $SUMMARY_FILE
if [ -n "$OUTPUT" ]; then
printf "%s\n" "$OUTPUT" >> $SUMMARY_FILE
else
printf "\n" >> $SUMMARY_FILE
fi
else
printf "## Post-processing failed ❌\n" >> $SUMMARY_FILE
printf "Exit code: %d\n" "$EXIT_CODE" >> $SUMMARY_FILE
if [ -n "$OUTPUT" ]; then
printf "\n" >> $SUMMARY_FILE
printf "Command output (including errors):\n" >> $SUMMARY_FILE
printf "```
printf "%s\n" "$OUTPUT" >> $SUMMARY_FILE
printf "```\n" >> $SUMMARY_FILE
fi
fi
# ---- PATCHED PREPARATION STEP START ----
- name: Prepare Students branch
run: |
git fetch origin Students || true
# Try to checkout Students or create it from origin/Students or origin/main
git checkout Students || git checkout -b Students origin/Students || git checkout -b Students origin/main
# Merge main if necessary, favoring their changes, allow unrelated histories
git merge origin/main --strategy-option=theirs --allow-unrelated-histories || true
git push origin Students --force
# Detect if Students has just been created and is identical to main
# In such case, force processing all notebooks in the next step
if [ "$(git rev-parse HEAD)" = "$(git rev-parse origin/main)" ]; then
echo "STUDENTS_FRESHLY_CREATED=true" >> "$GITHUB_ENV"
else
echo "STUDENTS_FRESHLY_CREATED=false" >> "$GITHUB_ENV"
fi
# ---- PATCHED PREPARATION STEP END ----
# ---- PATCHED DETECTION STEP START ----
- name: Detect notebooks changed on main vs Students
id: notebooks_changed
shell: bash
run: |
set -e
CONFIG_PATH=".github/conversion.json"
echo "[DEBUG] HEAD (Students) is at $(git rev-parse HEAD)"
echo "[DEBUG] origin/main is at $(git rev-parse origin/main)"
# Get monitored notebooks directories from config
DIRS=$(jq -r '.notebooks_dir[]' "$CONFIG_PATH")
echo "[DEBUG] Monitored directories:"
echo "$DIRS"
# Build regex for monitored directories
PATTERNS=""
for dir in $DIRS; do
dir=$(echo "$dir" | sed 's:/*$::')
if [ -n "$PATTERNS" ]; then
PATTERNS+="|"
fi
PATTERNS+="^${dir}/.*\\.ipynb$"
done
echo "[DEBUG] Regex pattern for grep: $PATTERNS"
# List all notebooks on main
NOTEBOOKS_ON_MAIN=$(git ls-tree -r --name-only origin/main | grep -E "$PATTERNS" || true)
echo "[DEBUG] Notebooks on main:"
echo "$NOTEBOOKS_ON_MAIN"
tmpfile=$(mktemp)
: > "$tmpfile"
# If the Students branch was just created (is equal to main), force processing all notebooks
if [[ "${STUDENTS_FRESHLY_CREATED}" == "true" ]]; then
echo "[INFO] Students is freshly created - force processing all notebooks."
echo "$NOTEBOOKS_ON_MAIN" > "$tmpfile"
else
# Compare each notebook and add changed ones to processing list
echo "$NOTEBOOKS_ON_MAIN" | while IFS= read -r nb; do
[ -z "$nb" ] && continue
echo "[DEBUG] Checking notebook '$nb' ..."
if ! git diff --quiet HEAD...origin/main -- "$nb"; then
echo "[DEBUG] '$nb': MODIFIED on main"
echo "$nb" >> "$tmpfile"
else
echo "[DEBUG] '$nb': unchanged"
fi
done
fi
echo "[DEBUG] Notebooks to process:"
cat "$tmpfile"
# Export list for next step
echo "notebooks<<EOF" >> $GITHUB_OUTPUT
cat "$tmpfile" >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
rm "$tmpfile"
# ---- PATCHED DETECTION STEP END ----
- name: Convert changed notebooks
if: steps.notebooks_changed.outputs.notebooks != ''
run: |
echo "[DEBUG] Notebooks to convert:"
echo "${{ steps.notebooks_changed.outputs.notebooks }}"
printf "## Processing Report\n" >> $SUMMARY_FILE
printf "| Notebook | Questions | Code Blocks | ZIP |\n" >> $SUMMARY_FILE
printf "|----------|-----------|-------------|------|\n" >> $SUMMARY_FILE
echo "${{ steps.notebooks_changed.outputs.notebooks }}" | while IFS= read -r nb; do
[ -z "$nb" ] && continue
echo "[DEBUG] Converting notebook: '$nb'"
git checkout origin/main -- "$nb"
uv run .github/scripts/student_version.py "$nb" \
--config "$CONFIG_PATH" \
--hide-header >> $SUMMARY_FILE
done
- name: Execute post-processing command
run: |
if [ -n "$POST_PROCESSING" ]; then
OUTPUT=$(bash -c "$POST_PROCESSING" 2>&1)
EXIT_CODE=$?
if [ $EXIT_CODE -eq 0 ]; then
printf "## Post-processing executed successfully ✅\n" >> $SUMMARY_FILE
if [ -n "$OUTPUT" ]; then
printf "%s\n" "$OUTPUT" >> $SUMMARY_FILE
else
printf "\n" >> $SUMMARY_FILE
fi
else
printf "## Post-processing failed ❌\n" >> $SUMMARY_FILE
printf "Exit code: %d\n" "$EXIT_CODE" >> $SUMMARY_FILE
if [ -n "$OUTPUT" ]; then
printf "\n" >> $SUMMARY_FILE
printf "Command output (including errors):\n" >> $SUMMARY_FILE
printf "```
printf "%s\n" "$OUTPUT" >> $SUMMARY_FILE
printf "```\n" >> $SUMMARY_FILE
fi
fi
fi
- name: manage summary
run: |
cp -a $SUMMARY_FILE .
cat README.md >> $GITHUB_STEP_SUMMARY
- name: Set up Git identity
run: |
git config --global user.name "GitHub Actions"
git config --global user.email "actions@github.com"
- name: Commit and push changes
run: |
# Only commit if there are actual changes (avoids empty commits)
git commit -m "Update student materials: $(date +'%Y-%m-%d %H:%M:%S')" || echo "No changes"
git push origin Students