Skip to content

Now handles correctly detection of only changed notebooks #256

Now handles correctly detection of only changed notebooks

Now handles correctly detection of only changed notebooks #256

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
- 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
- name: Debug changed notebooks detection
id: debug_notebooks
shell: bash
run: |
CONFIG_PATH=".github/conversion.json"
DIRS=$(jq -r '.notebooks_dir[]' "$CONFIG_PATH")
# Print context info for debug
echo "Github SHA context"
echo "------------------"
echo "GITHUB_EVENT_BEFORE=${GITHUB_EVENT_BEFORE}"
echo "GITHUB_EVENT_AFTER=${GITHUB_EVENT_AFTER}"
echo "github.event.before=${{ github.event.before }}"
echo "github.event.after=${{ github.event.after }}"
echo ""
echo "Commits in main branch (git log):"
git log --oneline -5
echo ""
BEFORE=${{ github.event.before }}
AFTER=${{ github.event.after }}
echo "BEFORE=$BEFORE"
echo "AFTER=$AFTER"
# Build grep pattern
PATTERNS=""
for dir in $DIRS; do
dir=$(echo "$dir" | sed 's:/*$::')
if [ -n "$PATTERNS" ]; then
PATTERNS+="|"
fi
PATTERNS+="^${dir}/.*\\.ipynb$"
done
echo "PATTERNS=$PATTERNS"
echo ""
echo "Diff files between BEFORE and AFTER:"
git diff --name-only "$BEFORE" "$AFTER"
echo ""
echo "Matched notebooks:"
git diff --name-only "$BEFORE" "$AFTER" | grep -E "$PATTERNS" || true
echo ""
# Write result to a file for step output
(git diff --name-only "$BEFORE" "$AFTER" | grep -E "$PATTERNS" || true) > notebooks_to_convert.txt
echo "notebooks<<EOF" >> $GITHUB_OUTPUT
cat notebooks_to_convert.txt >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
echo ""
echo "notebooks_to_convert.txt content:"
cat notebooks_to_convert.txt
- name: Debug notebooks_changed output variable
run: |
echo "GH_OUTPUT notebooks var BEGIN"
echo "::set-output name=notebooks::${{ steps.notebooks_changed.outputs.notebooks }}"
echo "Direct print with debug char :"
echo "${{ steps.notebooks_changed.outputs.notebooks }}" | cat -A
echo "GH_OUTPUT notebooks var END"
- name: Convert changed notebooks
run: |
echo "STEP notebooks variable =>"

Check failure on line 307 in .github/workflows/generate_student_version.yml

View workflow run for this annotation

GitHub Actions / .github/workflows/generate_student_version.yml

Invalid workflow file

You have an error in your yaml syntax on line 307
echo "${{ steps.notebooks_changed.outputs.notebooks }}" | cat -A
if [ -z "${{ steps.notebooks_changed.outputs.notebooks }}" ]; then
echo "VAR EVALUATED AS EMPTY"
else
echo "VAR NOT EMPTY"
fi
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