From a72fb266bd2d23371da17fc9506600eb99cc37cf Mon Sep 17 00:00:00 2001 From: Claude Code Date: Thu, 2 Oct 2025 16:17:28 -0700 Subject: [PATCH] Added file system compatibility validation to prevent folder/file names that break when syncing to local systems (Windows in particular): - Names ending with periods (.) or spaces - Reserved Windows names (CON, PRN, AUX, NUL, COM1-9, LPT1-9) - Invalid characters (< > : " | ? * or control characters) The workflow now detects these issues automatically and provides specific guidance in the PR comment before closing. --- .github/scripts/validate-structure.js | 28 ++++++++++++++++++++++ .github/workflows/validate-structure.yml | 30 +++++++++++++++++++++++- 2 files changed, 57 insertions(+), 1 deletion(-) diff --git a/.github/scripts/validate-structure.js b/.github/scripts/validate-structure.js index 12aac43534..ec4fb4541b 100644 --- a/.github/scripts/validate-structure.js +++ b/.github/scripts/validate-structure.js @@ -49,6 +49,34 @@ function validateFilePath(filePath) { const normalized = filePath.replace(/\\/g, '/'); const segments = normalized.split('/'); + // Check for invalid characters that break local file systems + for (let i = 0; i < segments.length; i++) { + const segment = segments[i]; + + // Check for trailing periods (invalid on Windows) + if (segment.endsWith('.')) { + return `Invalid folder/file name '${segment}' in path '${normalized}': Names cannot end with a period (.) as this breaks local file system sync on Windows.`; + } + + // Check for trailing spaces (invalid on Windows) + if (segment.endsWith(' ')) { + return `Invalid folder/file name '${segment}' in path '${normalized}': Names cannot end with a space as this breaks local file system sync on Windows.`; + } + + // Check for reserved Windows names + const reservedNames = ['CON', 'PRN', 'AUX', 'NUL', 'COM1', 'COM2', 'COM3', 'COM4', 'COM5', 'COM6', 'COM7', 'COM8', 'COM9', 'LPT1', 'LPT2', 'LPT3', 'LPT4', 'LPT5', 'LPT6', 'LPT7', 'LPT8', 'LPT9']; + const nameWithoutExt = segment.split('.')[0].toUpperCase(); + if (reservedNames.includes(nameWithoutExt)) { + return `Invalid folder/file name '${segment}' in path '${normalized}': '${nameWithoutExt}' is a reserved name on Windows and will break local file system sync.`; + } + + // Check for invalid characters (Windows and general file system restrictions) + const invalidChars = /[<>:"|?*\x00-\x1F]/; + if (invalidChars.test(segment)) { + return `Invalid folder/file name '${segment}' in path '${normalized}': Contains characters that are invalid on Windows file systems (< > : " | ? * or control characters).`; + } + } + if (!allowedCategories.has(segments[0])) { return null; } diff --git a/.github/workflows/validate-structure.yml b/.github/workflows/validate-structure.yml index 5b80e2a234..86979f70a7 100644 --- a/.github/workflows/validate-structure.yml +++ b/.github/workflows/validate-structure.yml @@ -65,6 +65,8 @@ jobs: cat "$tmp_error" >&2 if grep -q 'Folder structure violations found' "$tmp_output" "$tmp_error"; then + # Save validation output for use in PR comment + cat "$tmp_output" "$tmp_error" > "$RUNNER_TEMP/validation_output.txt" echo "status=failed" >> "$GITHUB_OUTPUT" exit 0 fi @@ -87,11 +89,37 @@ jobs: const owner = context.repo.owner; const repo = context.repo.repo; + const fs = require('fs'); + const output = fs.readFileSync(process.env.RUNNER_TEMP + '/validation_output.txt', 'utf8'); + + let commentBody = `Thank you for your contribution. However, it doesn't comply with our contributing guidelines.\n\n`; + + // Check if the error is about invalid file/folder names + if (output.includes('Names cannot end with a period') || + output.includes('Names cannot end with a space') || + output.includes('is a reserved name on Windows') || + output.includes('Contains characters that are invalid')) { + commentBody += `**❌ Invalid File/Folder Names Detected**\n\n`; + commentBody += `Your contribution contains file or folder names that will break when syncing to local file systems (especially Windows):\n\n`; + commentBody += `\`\`\`\n${output}\n\`\`\`\n\n`; + commentBody += `**Common issues:**\n`; + commentBody += `- Folder/file names ending with a period (.) - not allowed on Windows\n`; + commentBody += `- Folder/file names ending with spaces - not allowed on Windows\n`; + commentBody += `- Reserved names like CON, PRN, AUX, NUL, COM1-9, LPT1-9 - not allowed on Windows\n`; + commentBody += `- Invalid characters: < > : " | ? * or control characters\n\n`; + commentBody += `Please rename these files/folders to be compatible with all operating systems.\n\n`; + } else { + commentBody += `As a reminder, the general requirements (as outlined in the [CONTRIBUTING.md file](https://github.com/ServiceNowDevProgram/code-snippets/blob/main/CONTRIBUTING.md)) are the following: follow the folder+subfolder guidelines and include a README.md file explaining what the code snippet does.\n\n`; + commentBody += `**Validation errors:**\n\`\`\`\n${output}\n\`\`\`\n\n`; + } + + commentBody += `Review your contribution against the guidelines and make the necessary adjustments. Closing this for now. Once you make additional changes, feel free to re-open this Pull Request or create a new one.`; + await github.rest.issues.createComment({ owner, repo, issue_number: pullNumber, - body: `Thank you for your contribution. However, it doesn't comply with our contributing guidelines. As a reminder, the general requirements (as outlined in the [CONTRIBUTING.md file](https://github.com/ServiceNowDevProgram/code-snippets/blob/main/CONTRIBUTING.md)) are the following: follow the folder+subfolder guidelines and include a README.md file explaining what the code snippet does. Review your contribution against the guidelines and make the necessary adjustments. Closing this for now. Once you make additional changes, feel free to re-open this Pull Request or create a new one.`.trim() + body: commentBody.trim() }); await github.rest.pulls.update({