Instantly share code, notes, and snippets.
Created
March 25, 2025 21:19
-
Star
1
(1)
You must be signed in to star a gist -
Fork
1
(1)
You must be signed in to fork a gist
-
Save eneajaho/4c15276460911b0f7d880abf96ee0673 to your computer and use it in GitHub Desktop.
A migration to migrate all material buttons to use the input for the appearance instead of the selector
This file contains 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
const fs = require('fs').promises; | |
const path = require('path'); | |
const { EOL } = require('os'); // End Of Line character | |
// --- Configuration --- | |
const PROJECT_ROOT = process.argv[2] || '.'; // Get project root from arg or use current dir | |
const EXCLUDE_DIRS = ['node_modules', '.angular', 'dist', 'e2e', 'coverage']; // Directories to skip | |
const HTML_SUFFIX = '.html'; | |
const APPLY_CHANGES = process.argv.includes('--apply'); // Check for --apply flag | |
// --- End Configuration --- | |
console.log(`--- Angular Material Button Migration Script ---`); | |
if (APPLY_CHANGES) { | |
console.log("--- Mode: APPLY CHANGES ---"); | |
} else { | |
console.log("--- Mode: DRY RUN (use --apply to modify files) ---"); | |
} | |
console.log(`Project Root: ${path.resolve(PROJECT_ROOT)}`); | |
console.log(`Excluding: ${EXCLUDE_DIRS.join(', ')}`); | |
console.log("-------------------------------------------------"); | |
let totalFilesProcessed = 0; | |
let totalFilesChanged = 0; | |
/** | |
* Recursively finds files with a specific suffix in a directory, excluding specified paths. | |
*/ | |
async function findFiles(dir, suffix, exclude) { | |
// (Function unchanged) | |
let results = []; | |
try { | |
const list = await fs.readdir(dir, { withFileTypes: true }); | |
for (const dirent of list) { | |
const fullPath = path.resolve(dir, dirent.name); | |
const relativePath = path.relative(PROJECT_ROOT, fullPath); | |
if (exclude.some(ex => relativePath === ex || relativePath.startsWith(ex + path.sep))) { | |
continue; | |
} | |
if (dirent.isDirectory()) { | |
results = results.concat(await findFiles(fullPath, suffix, exclude)); | |
} else if (dirent.isFile() && dirent.name.endsWith(suffix)) { | |
results.push(fullPath); | |
} | |
} | |
} catch (err) { | |
if (err.code !== 'EACCES' && err.code !== 'EPERM') { | |
console.warn(`Warning: Could not read directory ${dir}: ${err.message}`); | |
} | |
} | |
return results; | |
} | |
/** | |
* MODIFIED: Replaces a legacy Material button attribute with the new matButton variant syntax. | |
* Removes the legacy attribute AND DOES NOT ensure 'mat-button' attribute is present. | |
* *** WARNING: This modification is likely incorrect for standard usage. *** | |
*/ | |
function replaceLegacyButton(content, legacyAttr, newVariant, filePath) { | |
const regex = new RegExp(`(<([a-zA-Z0-9\\-_]+)[^>]*?)(\\s+${legacyAttr})(\\s*[^>]*?>)`, 'g'); | |
let changed = false; | |
const lines = content.split(EOL); | |
const newLines = lines.map((line, index) => { | |
return line.replace(regex, (match, tagOpen, tagName, legacyAttrFull, tagEnd) => { | |
changed = true; | |
// --- MODIFICATION --- | |
// Intentionally do NOT add 'mat-button'. The baseButtonAttr is always empty. | |
const baseButtonAttr = ''; | |
// --- END MODIFICATION --- | |
// Construct the NEW string, omitting legacyAttrFull and baseButtonAttr | |
const replacement = `${tagOpen}${baseButtonAttr} matButton="${newVariant}"${tagEnd}`; | |
console.log(` [${path.basename(filePath)} L:${index + 1}] Removing '${legacyAttrFull.trim()}', Adding 'matButton="${newVariant}"' on <${tagName}> (WARN: Not ensuring mat-button)`); | |
return replacement; | |
}); | |
}); | |
return { newContent: newLines.join(EOL), changed }; | |
} | |
/** | |
* MODIFIED: Changes elements that have ONLY 'mat-button' (no matButton variant) | |
* to have ONLY 'matButton="text"', REMOVING the original 'mat-button'. | |
* *** WARNING: This modification is likely incorrect for standard usage. *** | |
*/ | |
function addTextVariant(content, filePath) { | |
// Find tags with 'mat-button' but NOT 'matButton=' | |
const regex = new RegExp(`(<([a-zA-Z0-9\\-_]+)[^>]*?)(\\smat-button\\b)(?![^>]*\\smatButton=)([^>]*?>)`, 'g'); | |
let changed = false; | |
const lines = content.split(EOL); | |
const newLines = lines.map((line, index) => { | |
return line.replace(regex, (match, tagOpen, tagName, matButtonAttr, tagEnd) => { | |
// matButtonAttr here contains the matched ' mat-button' | |
changed = true; | |
// --- MODIFICATION --- | |
// Construct replacement using ONLY the new variant, REMOVING the original mat-button | |
const replacement = `${tagOpen} matButton="text"${tagEnd}`; | |
// --- END MODIFICATION --- | |
console.log(` [${path.basename(filePath)} L:${index + 1}] Replacing '${matButtonAttr.trim()}' with 'matButton="text"' on <${tagName}> (WARN: Removed mat-button)`); | |
return replacement; | |
}); | |
}); | |
return { newContent: newLines.join(EOL), changed }; | |
} | |
/** | |
* Processes a single HTML file for button migrations. | |
*/ | |
async function processHtmlFile(filePath) { | |
// (Function unchanged logic, but uses modified replace/add functions) | |
const relativePath = path.relative(PROJECT_ROOT, filePath); | |
console.log(`Processing: ${relativePath}`); | |
totalFilesProcessed++; | |
try { | |
const originalContent = await fs.readFile(filePath, 'utf-8'); | |
let currentContent = originalContent; | |
let fileChanged = false; | |
let changes = []; // Store change descriptions | |
// Apply replacements using the MODIFIED functions | |
let resultStroked = replaceLegacyButton(currentContent, 'mat-stroked-button', 'outlined', filePath); | |
currentContent = resultStroked.newContent; | |
if (resultStroked.changed) changes.push('mat-stroked-button'); | |
let resultFlat = replaceLegacyButton(currentContent, 'mat-flat-button', 'filled', filePath); | |
currentContent = resultFlat.newContent; | |
if (resultFlat.changed) changes.push('mat-flat-button'); | |
let resultRaised = replaceLegacyButton(currentContent, 'mat-raised-button', 'elevated', filePath); | |
currentContent = resultRaised.newContent; | |
if (resultRaised.changed) changes.push('mat-raised-button'); | |
// Apply text variant addition using the MODIFIED function | |
let resultText = addTextVariant(currentContent, filePath); | |
currentContent = resultText.newContent; | |
if (resultText.changed) changes.push('mat-button -> matButton="text"'); | |
fileChanged = resultStroked.changed || resultFlat.changed || resultRaised.changed || resultText.changed; | |
if (fileChanged) { | |
totalFilesChanged++; | |
console.log(` -> Changes detected (${changes.join(', ')}) in ${relativePath}`); | |
if (APPLY_CHANGES) { | |
try { | |
await fs.writeFile(filePath, currentContent, 'utf-8'); | |
console.log(` -> SUCCESS: Updated ${relativePath}`); | |
} catch (writeErr) { | |
console.error(` -> ERROR: Failed to write changes to ${relativePath}: ${writeErr.message}`); | |
} | |
} else { | |
console.log(` -> DRY RUN: Would update ${relativePath}`); | |
} | |
} | |
} catch (error) { | |
console.error(`Error processing file ${relativePath}: ${error.message}`); | |
} | |
} | |
/** | |
* Main execution function. | |
*/ | |
async function main() { | |
// (Function unchanged) | |
const resolvedProjectRoot = path.resolve(PROJECT_ROOT); | |
try { | |
const htmlFiles = await findFiles(resolvedProjectRoot, HTML_SUFFIX, EXCLUDE_DIRS); | |
console.log(`Found ${htmlFiles.length} HTML files to analyze.`); | |
if (htmlFiles.length === 0) { | |
console.log("No HTML files found. Exiting."); | |
return; | |
} | |
// Process files sequentially | |
for (const file of htmlFiles) { | |
await processHtmlFile(file); | |
} | |
console.log("-------------------------------------------------"); | |
console.log("Migration Summary:"); | |
console.log(` Total HTML files scanned: ${totalFilesProcessed}`); | |
console.log(` Total HTML files ${APPLY_CHANGES ? 'modified' : 'needing changes'}: ${totalFilesChanged}`); | |
if (!APPLY_CHANGES && totalFilesChanged > 0) { | |
console.log("\nRun with the --apply flag to actually modify the files (Review WARNINGS first!)."); | |
} else if (APPLY_CHANGES && totalFilesChanged > 0) { | |
console.log("\nReview changes and test your application thoroughly for broken button styles!"); | |
} | |
console.log("-------------------------------------------------"); | |
} catch (error) { | |
console.error('\n--- An error occurred during the migration process ---'); | |
console.error(error); | |
process.exitCode = 1; // Indicate failure | |
} | |
} | |
// --- Run the script --- | |
main(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment