Fake update #249
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 |