-
Notifications
You must be signed in to change notification settings - Fork 3
feat: Add ops endpoints and refactor sync workflow #284
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
16df023
8b39b2f
6cfc3f8
3efbb20
22f1b5b
7574631
3cdb93d
8708255
d52b46e
fb74cb5
e14da74
6e613fd
1f635b9
f6dbccb
b6d49a6
4ca25fd
a7bf730
2f526f5
40dce20
289ef91
270a541
3f84aa3
326afe6
9fc679c
b5bba77
e0e6d9b
271cfb8
799ed75
972387a
706c734
32e215b
9a85bdc
7666a4f
ba36fa2
c034f5e
218b13a
6ec010a
1aa8ccd
674d271
f5bf1ab
7b37b69
8c484be
788ec8e
c139559
24b975d
a2e60a5
145d546
d3f7ca5
754a2c4
82702fa
fc608f7
35e0124
dda7b08
c5c05e0
6e7484d
737c117
4ff51ff
76760a3
64cef07
c9d1c5d
30a9045
763862d
5bd62ae
499511a
820c7f6
26c6311
7bb7150
9418647
5577736
a78e13a
5c36114
e380a11
e3bc319
93a3c8f
92ba9f9
4a4a5e0
df88d59
8e6d005
1557726
0406573
cdc46c7
841d6fc
88670ce
b4f756c
7681186
bf57622
0dc42e2
d89a665
680559d
acd932a
af43272
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,107 @@ | ||
| name: 'Sync Files' | ||
| description: 'Generic file/directory synchronization with filtering and exclusions' | ||
| inputs: | ||
| type: | ||
| description: 'Type identifier for the sync operation (for artifact naming)' | ||
| required: true | ||
| title: | ||
| description: 'Human-readable title for reports' | ||
| required: true | ||
| source_path: | ||
| description: 'Source file or directory path' | ||
| required: true | ||
| target_path: | ||
| description: 'Target file or directory path' | ||
| required: true | ||
| filter_pattern: | ||
| description: 'Regex pattern to filter files (only for directories)' | ||
| required: false | ||
| default: '.*' | ||
| exclude_files: | ||
| description: 'Comma-separated list of filenames to exclude' | ||
| required: false | ||
| default: 'README,CHANGELOG,.gitignore,.gitkeep' | ||
| sanitize_script: | ||
| description: 'Optional script path to run after sync (for sanitization/post-processing)' | ||
| required: false | ||
| default: '' | ||
| outputs: | ||
| added: | ||
| description: 'Number of files added' | ||
| value: ${{ steps.sync.outputs.added }} | ||
| updated: | ||
| description: 'Number of files updated' | ||
| value: ${{ steps.sync.outputs.updated }} | ||
| deleted: | ||
| description: 'Number of files deleted' | ||
| value: ${{ steps.sync.outputs.deleted }} | ||
| total: | ||
| description: 'Total number of changes' | ||
| value: ${{ steps.sync.outputs.total }} | ||
| runs: | ||
| using: 'composite' | ||
| steps: | ||
| - name: Prepare config file | ||
| if: inputs.type == 'config' | ||
| shell: bash | ||
| run: | | ||
| # For config type, handle special case: | ||
| # 1. Copy config.yaml.example to temp location as config.yaml | ||
| # 2. Run sanitization on the renamed file | ||
| # 3. Use the sanitized file as source for sync | ||
|
|
||
| if [[ -f "${{ inputs.source_path }}" ]]; then | ||
| # Create temp directory | ||
| TEMP_DIR=$(mktemp -d) | ||
| echo "TEMP_CONFIG_DIR=$TEMP_DIR" >> $GITHUB_ENV | ||
|
|
||
| # Copy and rename config.yaml.example to config.yaml | ||
| cp "${{ inputs.source_path }}" "$TEMP_DIR/config.yaml" | ||
|
|
||
| # Run sanitization if script is provided | ||
| if [[ -n "${{ inputs.sanitize_script }}" ]] && [[ -f "${{ inputs.sanitize_script }}" ]]; then | ||
| echo "Sanitizing config file..." | ||
| bash "${{ inputs.sanitize_script }}" "$TEMP_DIR/config.yaml" | ||
| fi | ||
|
|
||
| # Update source path for sync | ||
| echo "CONFIG_SOURCE=$TEMP_DIR/config.yaml" >> $GITHUB_ENV | ||
| else | ||
| echo "Config source file not found: ${{ inputs.source_path }}" | ||
| exit 1 | ||
| fi | ||
|
|
||
| - name: Sync files | ||
| id: sync | ||
| shell: bash | ||
| run: | | ||
| # Use prepared config source if it's a config type, otherwise use original source | ||
| if [[ "${{ inputs.type }}" == "config" ]] && [[ -n "$CONFIG_SOURCE" ]]; then | ||
| SOURCE_PATH="$CONFIG_SOURCE" | ||
| else | ||
| SOURCE_PATH="${{ inputs.source_path }}" | ||
| fi | ||
|
|
||
| ${{ github.action_path }}/sync.sh \ | ||
| "${{ inputs.type }}" \ | ||
| "${{ inputs.title }}" \ | ||
| "$SOURCE_PATH" \ | ||
| "${{ inputs.target_path }}" \ | ||
| "${{ inputs.filter_pattern }}" \ | ||
| "${{ inputs.exclude_files }}" | ||
|
|
||
| - name: Cleanup config temp directory | ||
| if: inputs.type == 'config' && always() | ||
| shell: bash | ||
| run: | | ||
| if [[ -n "$TEMP_CONFIG_DIR" ]] && [[ -d "$TEMP_CONFIG_DIR" ]]; then | ||
| rm -rf "$TEMP_CONFIG_DIR" | ||
| fi | ||
|
|
||
| - name: Upload artifacts | ||
| uses: actions/upload-artifact@v4 | ||
| if: always() | ||
| with: | ||
| name: synced-${{ inputs.type }} | ||
| path: artifacts/ | ||
| retention-days: 1 | ||
| Original file line number | Diff line number | Diff line change | ||||||
|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,238 @@ | ||||||||
| #!/bin/bash | ||||||||
| set -e | ||||||||
|
|
||||||||
| # File/directory synchronization script for documentation | ||||||||
| # Syncs .md and .mdx files with pattern filtering | ||||||||
|
|
||||||||
| # Get parameters | ||||||||
| TYPE="$1" | ||||||||
| TITLE="$2" | ||||||||
| SOURCE_PATH="$3" | ||||||||
| TARGET_PATH="$4" | ||||||||
| FILTER_PATTERN="${5:-.*}" | ||||||||
| EXCLUDE_FILES="${6:-README,CHANGELOG,.gitignore,.gitkeep}" | ||||||||
|
|
||||||||
| # Initialize metrics and lists | ||||||||
| added=0 | ||||||||
| updated=0 | ||||||||
| deleted=0 | ||||||||
| added_files=() | ||||||||
| updated_files=() | ||||||||
| deleted_files=() | ||||||||
|
|
||||||||
| # Convert exclusions to array | ||||||||
| IFS=',' read -ra EXCLUSIONS <<< "$EXCLUDE_FILES" | ||||||||
|
|
||||||||
| # Check if file should be excluded | ||||||||
| is_excluded() { | ||||||||
| local filename="$1" | ||||||||
| for excluded in "${EXCLUSIONS[@]}"; do | ||||||||
| [[ "$filename" == "$excluded" ]] && return 0 | ||||||||
| done | ||||||||
| return 1 | ||||||||
| } | ||||||||
|
|
||||||||
| # Check if filename matches pattern | ||||||||
| matches_pattern() { | ||||||||
| local filename="$1" | ||||||||
| local pattern="$2" | ||||||||
|
|
||||||||
| # Try perl first (supports PCRE including negative lookahead) | ||||||||
| if command -v perl >/dev/null 2>&1; then | ||||||||
| echo "$filename" | perl -ne "exit 0 if /^($pattern)\$/; exit 1" | ||||||||
| return $? | ||||||||
| fi | ||||||||
|
|
||||||||
| # Fallback to grep -E (doesn't support negative lookahead) | ||||||||
| echo "$filename" | grep -E "^($pattern)$" >/dev/null 2>&1 | ||||||||
| return $? | ||||||||
| } | ||||||||
|
|
||||||||
| # Handle single file sync | ||||||||
| if [[ -f "$SOURCE_PATH" ]]; then | ||||||||
| echo "Syncing file: $SOURCE_PATH -> $TARGET_PATH" | ||||||||
| mkdir -p "$(dirname "$TARGET_PATH")" | ||||||||
|
|
||||||||
| if [[ -f "$TARGET_PATH" ]]; then | ||||||||
| if ! cmp -s "$SOURCE_PATH" "$TARGET_PATH"; then | ||||||||
| cp "$SOURCE_PATH" "$TARGET_PATH" | ||||||||
| updated=1 | ||||||||
| updated_files+=("$(basename "$TARGET_PATH")") | ||||||||
| echo "Updated: $(basename "$TARGET_PATH")" | ||||||||
| fi | ||||||||
| else | ||||||||
| cp "$SOURCE_PATH" "$TARGET_PATH" | ||||||||
| added=1 | ||||||||
| added_files+=("$(basename "$TARGET_PATH")") | ||||||||
| echo "Added: $(basename "$TARGET_PATH")" | ||||||||
| fi | ||||||||
|
|
||||||||
| # Handle directory sync | ||||||||
| elif [[ -d "$SOURCE_PATH" ]]; then | ||||||||
| echo "Syncing directory: $SOURCE_PATH -> $TARGET_PATH" | ||||||||
| echo "Filter pattern: $FILTER_PATTERN" | ||||||||
| echo "Exclude files: $EXCLUDE_FILES" | ||||||||
|
|
||||||||
| # Check if source directory has any files | ||||||||
| if [[ -z "$(ls -A "$SOURCE_PATH" 2>/dev/null)" ]]; then | ||||||||
| echo "Warning: Source directory is empty: $SOURCE_PATH" | ||||||||
| # Create empty target to ensure it exists | ||||||||
| mkdir -p "$TARGET_PATH" | ||||||||
| added=0 | ||||||||
| updated=0 | ||||||||
| deleted=0 | ||||||||
| else | ||||||||
| mkdir -p "$TARGET_PATH" | ||||||||
|
|
||||||||
| # Create temp directory with normalized source files | ||||||||
| TEMP_SOURCE=$(mktemp -d) | ||||||||
| trap "rm -rf $TEMP_SOURCE" EXIT | ||||||||
|
|
||||||||
|
Comment on lines
+89
to
+90
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Fix trap quoting to avoid SC2064 and accidental early expansion Use single quotes so the variable expands at signal time and quote the arg inside. - trap "rm -rf $TEMP_SOURCE" EXIT
+ trap 'rm -rf "$TEMP_SOURCE"' EXIT📝 Committable suggestion
Suggested change
🧰 Tools🪛 Shellcheck (0.10.0)[warning] 89-89: Use single quotes, otherwise this expands now rather than when signalled. (SC2064) 🤖 Prompt for AI Agents |
||||||||
| echo "Preparing source files..." | ||||||||
|
|
||||||||
| # Count source files | ||||||||
| source_count=0 | ||||||||
|
|
||||||||
| # Process and filter source files into temp directory | ||||||||
| shopt -s nullglob # Handle case when no .md or .mdx files exist | ||||||||
| for file in "$SOURCE_PATH"/*.md "$SOURCE_PATH"/*.mdx; do | ||||||||
| [[ ! -f "$file" ]] && continue | ||||||||
|
|
||||||||
| basename_file=$(basename "$file") | ||||||||
| basename_no_ext="${basename_file%.*}" | ||||||||
|
|
||||||||
| # Skip excluded files | ||||||||
| is_excluded "$basename_no_ext" && continue | ||||||||
|
|
||||||||
| # Skip if doesn't match pattern | ||||||||
| matches_pattern "$basename_no_ext" "$FILTER_PATTERN" || continue | ||||||||
|
|
||||||||
| # Copy to temp with .mdx extension | ||||||||
| cp "$file" "$TEMP_SOURCE/${basename_no_ext}.mdx" | ||||||||
| source_count=$((source_count + 1)) | ||||||||
| echo " Processing: $basename_file" | ||||||||
| done | ||||||||
| shopt -u nullglob | ||||||||
|
|
||||||||
| echo "Processed $source_count source files" | ||||||||
|
|
||||||||
| # Track existing target files (using simple array for compatibility) | ||||||||
| existing_files=() | ||||||||
| if [[ -d "$TARGET_PATH" ]]; then | ||||||||
| while IFS= read -r file; do | ||||||||
| [[ -n "$file" ]] && existing_files+=("$file") | ||||||||
| done < <(find "$TARGET_PATH" -type f -name "*.mdx" 2>/dev/null) | ||||||||
| fi | ||||||||
|
|
||||||||
| # Sync from temp source to target | ||||||||
| for source_file in "$TEMP_SOURCE"/*.mdx; do | ||||||||
| [[ ! -f "$source_file" ]] && continue | ||||||||
|
|
||||||||
| basename_file=$(basename "$source_file") | ||||||||
| target_file="$TARGET_PATH/$basename_file" | ||||||||
|
|
||||||||
| if [[ -f "$target_file" ]]; then | ||||||||
| if ! cmp -s "$source_file" "$target_file"; then | ||||||||
| cp "$source_file" "$target_file" | ||||||||
| updated=$((updated + 1)) | ||||||||
| updated_files+=("$basename_file") | ||||||||
| echo "Updated: $basename_file" | ||||||||
| fi | ||||||||
| # Remove from existing files array | ||||||||
| new_existing=() | ||||||||
| for ef in "${existing_files[@]}"; do | ||||||||
| [[ "$(basename "$ef")" != "$basename_file" ]] && new_existing+=("$ef") | ||||||||
| done | ||||||||
| existing_files=("${new_existing[@]}") | ||||||||
| else | ||||||||
| cp "$source_file" "$target_file" | ||||||||
| added=$((added + 1)) | ||||||||
| added_files+=("$basename_file") | ||||||||
| echo "Added: $basename_file" | ||||||||
| fi | ||||||||
| done | ||||||||
|
|
||||||||
| # Delete orphaned files only if we had source files to sync | ||||||||
| if [[ "$source_count" -gt 0 ]]; then | ||||||||
| # Safe to delete orphaned files (preserve _meta.json) | ||||||||
| for target_file in "${existing_files[@]}"; do | ||||||||
| if [[ -f "$target_file" && "$(basename "$target_file")" != "_meta.json" ]]; then | ||||||||
| rm "$target_file" | ||||||||
| deleted=$((deleted + 1)) | ||||||||
| deleted_files+=("$(basename "$target_file")") | ||||||||
| echo "Deleted: $(basename "$target_file")" | ||||||||
| fi | ||||||||
| done | ||||||||
| else | ||||||||
| echo "Warning: No source files matched filter; skipping deletion phase for safety" | ||||||||
| echo " This prevents accidental mass deletion of target files" | ||||||||
| fi | ||||||||
| fi # End of non-empty directory check | ||||||||
| else | ||||||||
| echo "Source not found: $SOURCE_PATH" | ||||||||
| exit 1 | ||||||||
| fi | ||||||||
|
|
||||||||
| # Output metrics | ||||||||
| total=$((added + updated + deleted)) | ||||||||
| echo "added=$added" >> "$GITHUB_OUTPUT" | ||||||||
| echo "updated=$updated" >> "$GITHUB_OUTPUT" | ||||||||
| echo "deleted=$deleted" >> "$GITHUB_OUTPUT" | ||||||||
| echo "total=$total" >> "$GITHUB_OUTPUT" | ||||||||
|
|
||||||||
| # Create artifact directory with report and synced files | ||||||||
| mkdir -p artifacts | ||||||||
|
|
||||||||
| # Create summary report at root of artifacts | ||||||||
| REPORT_FILE="artifacts/sync_report_${TYPE}.md" | ||||||||
| cat > "$REPORT_FILE" <<EOF | ||||||||
| ## ${TITLE} | ||||||||
|
|
||||||||
| ### Summary | ||||||||
| - **Added**: $added files | ||||||||
| - **Updated**: $updated files | ||||||||
| - **Deleted**: $deleted files | ||||||||
| - **Total changes**: $total | ||||||||
|
|
||||||||
| ### Synced Files Location | ||||||||
| Path: \`${TARGET_PATH}\` | ||||||||
| EOF | ||||||||
|
|
||||||||
| # Add file lists to report | ||||||||
| if [[ ${#added_files[@]} -gt 0 ]]; then | ||||||||
| echo "" >> "$REPORT_FILE" | ||||||||
| echo "### Added Files" >> "$REPORT_FILE" | ||||||||
| for file in "${added_files[@]}"; do | ||||||||
| echo "- $file" >> "$REPORT_FILE" | ||||||||
| done | ||||||||
| fi | ||||||||
|
|
||||||||
| if [[ ${#updated_files[@]} -gt 0 ]]; then | ||||||||
| echo "" >> "$REPORT_FILE" | ||||||||
| echo "### Updated Files" >> "$REPORT_FILE" | ||||||||
| for file in "${updated_files[@]}"; do | ||||||||
| echo "- $file" >> "$REPORT_FILE" | ||||||||
| done | ||||||||
| fi | ||||||||
|
|
||||||||
| if [[ ${#deleted_files[@]} -gt 0 ]]; then | ||||||||
| echo "" >> "$REPORT_FILE" | ||||||||
| echo "### Deleted Files" >> "$REPORT_FILE" | ||||||||
| for file in "${deleted_files[@]}"; do | ||||||||
| echo "- $file" >> "$REPORT_FILE" | ||||||||
| done | ||||||||
| fi | ||||||||
|
|
||||||||
| # Copy whatever is in target to artifacts | ||||||||
| if [[ -f "$TARGET_PATH" ]]; then | ||||||||
| # Single file | ||||||||
| mkdir -p "artifacts/$(dirname "$TARGET_PATH")" | ||||||||
| cp "$TARGET_PATH" "artifacts/${TARGET_PATH}" | ||||||||
|
|
||||||||
| elif [[ -d "$TARGET_PATH" ]]; then | ||||||||
| # Directory - just copy everything | ||||||||
| mkdir -p "artifacts/${TARGET_PATH}" | ||||||||
| cp -r "$TARGET_PATH"/* "artifacts/${TARGET_PATH}/" 2>/dev/null || true | ||||||||
| fi | ||||||||
|
|
||||||||
| echo "Sync completed: $total changes" | ||||||||
Uh oh!
There was an error while loading. Please reload this page.