diff --git a/.changesets/20250317T225453-pr-4.md b/.changesets/20250317T225453-pr-4.md new file mode 100644 index 0000000..1e802a5 --- /dev/null +++ b/.changesets/20250317T225453-pr-4.md @@ -0,0 +1,9 @@ +--- +title: "feat: test tag updates" +pr: 4 +author: "jasonbahl" +type: "feat" +breaking: false +description: | + null +--- diff --git a/.github/workflows/UPDATE_SINCE_TAGS.md b/.github/workflows/UPDATE_SINCE_TAGS.md new file mode 100644 index 0000000..ba07ae0 --- /dev/null +++ b/.github/workflows/UPDATE_SINCE_TAGS.md @@ -0,0 +1,100 @@ +# @since Tag Update Implementation Plan + +## Overview + +This document outlines the plan for implementing automated `@since` tag updates during the release process. The system will update placeholder version tags (such as `@since todo`, `@since next-version`, and `@since tbd`) with the actual version number during releases. + +## Implementation Checklist + +### 1. Package.json Updates āœ… +- [x] Add `chalk` to devDependencies +- [x] Add a new npm script for running the update-since-tags.js (e.g., `since-tags:update`) + +### 2. Script Enhancements (update-since-tags.js) āœ… +- [x] Add better console output formatting using chalk +- [x] Enhance logging to provide detailed information about updated files +- [x] Add functionality to generate a summary of updates for release notes +- [x] Add documentation for supported placeholders (`todo`, `next-version`, `tbd`) +- [x] Add error handling for file operations +- [x] Add validation for version number input + +### 3. Testing āœ… +- [x] Test script locally with various scenarios +- [x] Test with multiple @since tags in single file +- [x] Test with no @since tags present +- [ ] Test workflow with actual PR and release +- [ ] Test error scenarios in workflow context + +### 4. Release Management Workflow Updates (Next Steps) šŸš€ +- [x] Add new step in release-management.yml after version bump +- [x] Integrate since-tag updates into the workflow +- [x] Add logging output to release notes +- [x] Handle potential errors gracefully +- [x] Ensure changes are committed with version bump + +### 5. Changeset Integration +- [ ] Modify generate-changeset.yml to detect files with @since placeholders +- [ ] Add @since placeholder information to changeset content +- [ ] Update release PR template to include @since placeholder information +- [ ] Ensure this information flows through to final release notes + +### 6. Documentation Updates +- [ ] Update SUMMARY.md with new functionality +- [ ] Update main README.md with @since tag information +- [ ] Update workflow documentation +- [ ] Add examples of using @since placeholders +- [ ] Document supported file types (PHP only for now) + +## Script Enhancements Completed āœ… + +The `update-since-tags.js` script has been enhanced with: +- Improved console output using chalk for better readability +- Detailed logging of file updates and error conditions +- Release notes summary generation +- Version number validation +- Error handling for file operations +- Support for counting and reporting the number of updates per file +- Temporary file creation for workflow integration + +## Local Testing Results āœ… + +The script has been successfully tested locally with: +- Multiple files containing @since placeholders +- Files with multiple placeholders +- Files with no placeholders +- Proper version number validation +- Summary generation for release notes +- Colored console output for better readability + +## Supported Placeholders + +The following placeholders will be automatically updated during release: +- `@since todo` +- `@since next-version` +- `@since tbd` + +## File Types + +Currently, the system only scans PHP files for @since placeholders. This may be expanded in future versions. + +## Notes + +- The script currently works as-is with CommonJS modules +- We're focusing on PHP files only for the initial implementation +- Changes are being made incrementally to avoid disrupting existing workflows +- Each change is tested thoroughly before moving to the next item + +## Next Steps šŸš€ + +1. Integrate the script into release-management.yml workflow +2. Test the integration with a real PR and release +3. Implement changeset integration for @since placeholder tracking +4. Update all documentation + +## Future Considerations + +- Support for additional file types (js, jsx, tsx, etc.) +- Support for additional placeholder formats +- Integration with other documentation tools +- Automated testing for the script +- Performance optimization for large codebases \ No newline at end of file diff --git a/.github/workflows/release-management.yml b/.github/workflows/release-management.yml index e2b001d..880b694 100644 --- a/.github/workflows/release-management.yml +++ b/.github/workflows/release-management.yml @@ -92,6 +92,31 @@ jobs: NEW_VERSION=$(grep -oP "define\('AUTOMATION_TESTS_VERSION', '\K[^']+" constants.php) echo "version=${NEW_VERSION}" >> $GITHUB_OUTPUT + - name: Update @since tags + id: update_since_tags + run: | + # Run the since-tags update script with the new version + npm run since-tags:update -- ${{ steps.version_bump.outputs.version }} + + # Check if summary file exists and has content + if [ -f "/tmp/since-tags-summary.md" ]; then + # Read the summary file + SINCE_SUMMARY=$(cat /tmp/since-tags-summary.md) + + # Properly escape the content for GitHub Actions + SINCE_SUMMARY="${SINCE_SUMMARY//'%'/'%25'}" + SINCE_SUMMARY="${SINCE_SUMMARY//$'\n'/'%0A'}" + SINCE_SUMMARY="${SINCE_SUMMARY//$'\r'/'%0D'}" + + # Set the output + echo "has_updates=true" >> $GITHUB_OUTPUT + echo "summary<> $GITHUB_OUTPUT + echo "$SINCE_SUMMARY" >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT + else + echo "has_updates=false" >> $GITHUB_OUTPUT + fi + - name: Update changelogs run: | # First, check if this is a breaking change release by using our new script @@ -125,6 +150,12 @@ jobs: npm run release:notes 2>/dev/null | grep -v "^>" > /tmp/release-notes/release_notes.md fi + # Check if we have @since tag updates to append + if [[ "${{ steps.update_since_tags.outputs.has_updates }}" == "true" ]]; then + echo "" >> /tmp/release-notes/release_notes.md + echo "${{ steps.update_since_tags.outputs.summary }}" >> /tmp/release-notes/release_notes.md + fi + # Check if the file has content if [ ! -s /tmp/release-notes/release_notes.md ]; then # If empty, provide a default message diff --git a/automation-tests.php b/automation-tests.php index 3e17b96..3d1cec5 100644 --- a/automation-tests.php +++ b/automation-tests.php @@ -19,4 +19,13 @@ // New Feature 1 +/** + * Testing a new feature with a since tag + * + * @since next-version + * @deprecated @since next-version This function was deprecated when it was added because it was just a test. + */ +function test_since_next_version() { + _deprecated_function( 'test_since_next_version', '@since next-version', '' ) +} diff --git a/package-lock.json b/package-lock.json index ea68af5..df6871f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,15 +1,16 @@ { "name": "automation-tests", - "version": "2.0.0", + "version": "4.0.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "automation-tests", - "version": "2.0.0", + "version": "4.0.2", "license": "GPL-2.0-or-later", "devDependencies": { "archiver": "^5.3.1", + "chalk": "^4.1.2", "dotenv": "^16.4.7", "fs-extra": "^11.1.1", "glob": "^10.3.3", @@ -283,6 +284,39 @@ "node": "*" } }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chalk/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, "node_modules/cliui": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", @@ -600,6 +634,16 @@ "dev": true, "license": "ISC" }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/ieee754": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", @@ -1097,6 +1141,19 @@ "node": ">=8" } }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/tar-stream": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", diff --git a/package.json b/package.json index 8b90f7e..fa19857 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,8 @@ "build": "node scripts/build.js", "release:prepare": "npm run version:bump && npm run changelogs:update", "test": "echo \"Error: no test specified\" && exit 1", - "upgrade-notice:update": "node scripts/update-upgrade-notice.js" + "upgrade-notice:update": "node scripts/update-upgrade-notice.js", + "since-tags:update": "node scripts/update-since-tags.js" }, "repository": { "type": "git", @@ -35,6 +36,7 @@ "homepage": "https://github.com/jasonbahl/automation-tests#readme", "devDependencies": { "archiver": "^5.3.1", + "chalk": "^4.1.2", "dotenv": "^16.4.7", "fs-extra": "^11.1.1", "glob": "^10.3.3", diff --git a/scripts/update-since-tags.js b/scripts/update-since-tags.js new file mode 100644 index 0000000..da5c154 --- /dev/null +++ b/scripts/update-since-tags.js @@ -0,0 +1,203 @@ +const fs = require('fs'); +const path = require('path'); +const { glob } = require('glob'); +const chalk = require('chalk'); + +/** + * Find files containing @since todo tags + */ +async function findSinceTodoFiles(pattern = 'src/**/*.php') { + try { + console.log(chalk.blue('\nScanning for @since placeholder tags...')); + console.log(chalk.gray('Looking for files matching pattern:', pattern)); + + const files = glob.sync(pattern, { + ignore: [ + 'node_modules/**', + 'vendor/**', + 'phpcs/**', + '.github/**', + '.wordpress-org/**', + 'bin/**', + 'build/**', + 'docker/**', + 'img/**', + 'phpstan/**', + 'docs/**' + ], + dot: false, + cwd: process.cwd() + }); + + console.log(chalk.gray(`Found ${files.length} PHP files to scan`)); + return files || []; + } catch (error) { + console.error(chalk.red('Error finding files:', error.message)); + return []; + } +} + +/** + * Get all @since placeholders from a file + */ +function getSincePlaceholders(content) { + const regex = /@since\s+(todo|next-version|tbd)|@next-version/gi; + const matches = content.match(regex); + return matches ? matches.length : 0; +} + +/** + * Update @since placeholders in a file + */ +function updateSinceTags(filePath, version) { + try { + let content = fs.readFileSync(filePath, 'utf8'); + const originalContent = content; + const placeholderCount = getSincePlaceholders(content); + + if (placeholderCount === 0) { + return { updated: false, count: 0 }; + } + + content = content.replace( + /@since\s+(todo|tbd|next-version)|@next-version/gi, + `@since ${version}` + ); + + if (content !== originalContent) { + fs.writeFileSync(filePath, content); + return { updated: true, count: placeholderCount }; + } + + return { updated: false, count: 0 }; + } catch (error) { + throw new Error(`Error updating ${filePath}: ${error.message}`); + } +} + +/** + * Update all @since todo tags in the project + */ +async function updateAllSinceTags(version, pattern = '**/*.php') { + const results = { + updated: [], + errors: [], + totalUpdated: 0 + }; + + try { + if (!version) { + throw new Error('Version argument is required'); + } + + if (!version.match(/^\d+\.\d+\.\d+(?:-[\w.-]+)?$/)) { + throw new Error('Invalid version format. Expected format: x.y.z or x.y.z-beta.n'); + } + + const files = await findSinceTodoFiles(pattern); + + for (const file of files) { + try { + const { updated, count } = updateSinceTags(file, version); + if (updated) { + results.updated.push({ file, count }); + results.totalUpdated += count; + } + } catch (error) { + results.errors.push({ file, error: error.message }); + } + } + + return results; + } catch (error) { + throw new Error(`Error updating @since tags: ${error.message}`); + } +} + +/** + * Generate a summary for release notes + */ +function generateReleaseNotesSummary(results) { + if (results.totalUpdated === 0) { + return ''; + } + + let summary = '### @since Tag Updates\n\n'; + summary += `Updated ${results.totalUpdated} @since placeholder`; + summary += results.totalUpdated === 1 ? '' : 's'; + summary += ' in the following files:\n\n'; + + results.updated.forEach(({ file, count }) => { + summary += `- \`${file}\` (${count} update${count === 1 ? '' : 's'})\n`; + }); + + if (results.errors.length > 0) { + summary += '\n#### Errors\n\n'; + results.errors.forEach(({ file, error }) => { + summary += `- Failed to update \`${file}\`: ${error}\n`; + }); + } + + return summary; +} + +/** + * CLI command to update @since tags + */ +async function main() { + try { + const version = process.argv[2]; + if (!version) { + throw new Error('Version argument is required'); + } + + console.log(chalk.blue('\nUpdating @since placeholder tags...')); + const results = await updateAllSinceTags(version); + + if (results.updated.length > 0) { + console.log(chalk.green('\nāœ“ Updated files:')); + results.updated.forEach(({ file, count }) => { + console.log(chalk.gray(` - ${path.relative(process.cwd(), file)} (${count} update${count === 1 ? '' : 's'})`)); + }); + console.log(chalk.green(`\nTotal placeholders updated: ${results.totalUpdated}`)); + } else { + console.log(chalk.yellow('\nNo @since placeholder tags found')); + } + + if (results.errors.length > 0) { + console.log(chalk.red('\nāŒ Errors:')); + results.errors.forEach(({ file, error }) => { + console.log(chalk.gray(` - ${path.relative(process.cwd(), file)}: ${error}`)); + }); + process.exit(1); + } + + // Generate release notes summary + const summary = generateReleaseNotesSummary(results); + if (summary) { + // Save summary to a temporary file for the workflow to use + const summaryPath = '/tmp/since-tags-summary.md'; + fs.writeFileSync(summaryPath, summary); + console.log(chalk.blue('\nSummary saved to:', summaryPath)); + } + + process.exit(0); + } catch (error) { + console.error(chalk.red('\nāŒ Error:'), error.message); + process.exit(1); + } +} + +// Run if called directly +if (require.main === module) { + main(); +} + +// Export functions for testing and reuse +module.exports = { + findSinceTodoFiles, + getSincePlaceholders, + updateSinceTags, + updateAllSinceTags, + generateReleaseNotesSummary +}; \ No newline at end of file