|
| 1 | +const childProcess = require('child_process'); |
| 2 | +const path = require('path'); |
| 3 | +const fs = require('fs'); |
| 4 | +const {sync: glob} = require('glob'); |
| 5 | + |
| 6 | +const directory = path.join(__dirname, '../src'); |
| 7 | +const migratedFiles = new Set(); |
| 8 | +const ignorePatterns = [ |
| 9 | + '**/*.import.scss', |
| 10 | + '**/test-theming-bundle.scss', |
| 11 | + 'material/_theming.scss' |
| 12 | +]; |
| 13 | +const materialPrefixes = [ |
| 14 | + ...getPrefixes('material', 'mat'), |
| 15 | + ...getPrefixes('material/core', 'mat'), |
| 16 | + // Outliers that don't have a directory of their own. |
| 17 | + 'mat-pseudo-checkbox-', |
| 18 | + 'mat-elevation-', |
| 19 | + 'mat-optgroup-', |
| 20 | + 'mat-private-' |
| 21 | +]; |
| 22 | +const mdcPrefixes = [ |
| 23 | + ...getPrefixes('material-experimental', 'mat'), |
| 24 | + ...getPrefixes('material-experimental/mdc-core', 'mat'), |
| 25 | + // Outliers that don't have a directory of their own. |
| 26 | + 'mat-mdc-optgroup-' |
| 27 | +].map(prefix => prefix === 'mat-' ? 'mat-mdc-' : prefix); |
| 28 | +const cdkPrefixes = getPrefixes('cdk', 'cdk'); |
| 29 | +const cdkExperimentalPrefixes = getPrefixes('cdk-experimental', 'cdk'); |
| 30 | + |
| 31 | +// Restore the source directory to a clean state. |
| 32 | +run('git', ['clean', '-f', '-d'], false, true); |
| 33 | +run('git', ['checkout', '--', directory], false, true); |
| 34 | + |
| 35 | +// --reset is a utility to easily restore the repo to its initial state. |
| 36 | +if (process.argv.indexOf('--reset') > -1) { |
| 37 | + process.exit(0); |
| 38 | +} |
| 39 | + |
| 40 | +// Run the migrations. |
| 41 | + |
| 42 | +// Migrate all the partials and forward any export symbols. |
| 43 | +migrate('cdk/**/_*.scss', cdkPrefixes, true); |
| 44 | +migrate('cdk-experimental/**/_*.scss', cdkExperimentalPrefixes, true); |
| 45 | +migrate('material/core/**/_*.scss', materialPrefixes, true, ['**/_all-*.scss', '**/_core.scss']); |
| 46 | +migrate('material/!(core)/**/_*.scss', materialPrefixes, true); |
| 47 | +migrate('material/core/**/_*.scss', materialPrefixes, true); |
| 48 | + |
| 49 | +// Comment out all MDC imports since the migrator script doesn't know how to find them. |
| 50 | +commentOutMdc('material-experimental/**/*.scss'); |
| 51 | + |
| 52 | +// Migrate all of the MDC partials. |
| 53 | +migrate('material-experimental/mdc-helpers/**/_*.scss', mdcPrefixes, true); |
| 54 | +migrate('material-experimental/mdc-core/**/_*.scss', mdcPrefixes, true, ['**/_core.scss']); |
| 55 | +migrate('material-experimental/**/_*.scss', mdcPrefixes, true); |
| 56 | + |
| 57 | +// Migrate everything else without forwarding. |
| 58 | +migrate('cdk/**/*.scss', cdkPrefixes); |
| 59 | +migrate('cdk-experimental/**/*.scss', cdkExperimentalPrefixes); |
| 60 | +migrate('material/**/*.scss', materialPrefixes); |
| 61 | +migrate('material-experimental/**/*.scss', mdcPrefixes); |
| 62 | + |
| 63 | +// Migrate whatever is left in the source files, assuming that it's not a public API. |
| 64 | +migrate('**/*.scss'); |
| 65 | + |
| 66 | +// Restore the commented out MDC imports and sort `@use` above `@import`. |
| 67 | +restoreAndSortMdc('material-experimental/**/*.scss'); |
| 68 | + |
| 69 | +// Clear the files that we don't want. |
| 70 | +clearUnwatedFiles(); |
| 71 | + |
| 72 | +// Try to auto-fix some of the lint issues. |
| 73 | +run('yarn', ['stylelint', '--fix'], true, true); |
| 74 | +console.log(`Finished migrating ${migratedFiles.size} files.`); |
| 75 | + |
| 76 | +function migrate(pattern, prefixes = [], forward = false, ignore = []) { |
| 77 | + const args = ['module']; |
| 78 | + forward && args.push('--forward=import-only'); |
| 79 | + prefixes.length && args.push(`--remove-prefix=${prefixes.join(',')}`); |
| 80 | + |
| 81 | + // Note that while the migrator allows for multiple files to be passed in, we start getting |
| 82 | + // some assertion errors along the way. Running it on a file-by-file basis works fine. |
| 83 | + const files = glob(pattern, {cwd: directory, ignore: [...ignore, ...ignorePatterns]}) |
| 84 | + .filter(file => !migratedFiles.has(file)); |
| 85 | + const message = `Migrating ${files.length} unmigrated files matching ${pattern}.`; |
| 86 | + console.log(ignore.length ? message + ` Ignoring ${ignore.join(', ')}.` : message); |
| 87 | + run('sass-migrator', [...args, ...files]); |
| 88 | + files.forEach(file => migratedFiles.add(file)); |
| 89 | +} |
| 90 | + |
| 91 | +function run(name, args, canFail = false, silent = false) { |
| 92 | + const result = childProcess.spawnSync(name, args, {shell: true, cwd: directory}); |
| 93 | + const output = result.stdout.toString(); |
| 94 | + !silent && output.length && console.log(output); |
| 95 | + |
| 96 | + if (result.status !== 0 && !canFail) { |
| 97 | + console.error(`Script error: ${(result.stderr || result.stdout)}`); |
| 98 | + process.exit(1); |
| 99 | + } |
| 100 | +} |
| 101 | + |
| 102 | +function getPrefixes(package, prefix) { |
| 103 | + return fs.readdirSync(path.join(directory, package), {withFileTypes: true}) |
| 104 | + .filter(current => current.isDirectory()) |
| 105 | + .map(current => current.name) |
| 106 | + .reduce((output, current) => [`${prefix}-${current}-`, ...output], [`${prefix}-`]); |
| 107 | +} |
| 108 | + |
| 109 | +function commentOutMdc(pattern) { |
| 110 | + const files = glob(pattern, {cwd: directory, absolute: true}); |
| 111 | + console.log(`Commenting out @material imports from ${files.length} files matching ${pattern}.`); |
| 112 | + files.forEach(file => { |
| 113 | + const content = fs.readFileSync(file, 'utf8'); |
| 114 | + // Prefix the content with a marker so we know what to restore later. |
| 115 | + fs.writeFileSync(file, content.replace(/(@use|@import) '@material/g, m => '//🚀 ' + m)); |
| 116 | + }); |
| 117 | +} |
| 118 | + |
| 119 | +function restoreAndSortMdc(pattern) { |
| 120 | + const files = glob(pattern, {cwd: directory, absolute: true}); |
| 121 | + console.log(`Re-adding and sorting @material imports from ${files.length} ` + |
| 122 | + `files matching ${pattern}.`); |
| 123 | + |
| 124 | + files.forEach(file => { |
| 125 | + // Remove the commented out lines with the marker from `commentOutMdc`. |
| 126 | + const content = fs.readFileSync(file, 'utf8').replace(/\/\/🚀 /g, ''); |
| 127 | + const lines = content.split('\n'); |
| 128 | + let headerStartIndex = -1; |
| 129 | + let headerEndIndex = -1; |
| 130 | + |
| 131 | + // Find where the comments start and end. |
| 132 | + for (let i = lines.length - 1; i > -1; i--) { |
| 133 | + if (lines[i].startsWith('@use') || lines[i].startsWith('@import')) { |
| 134 | + headerStartIndex = i; |
| 135 | + |
| 136 | + if (headerEndIndex === -1) { |
| 137 | + headerEndIndex = i + 1; |
| 138 | + } |
| 139 | + } |
| 140 | + } |
| 141 | + |
| 142 | + // Sort the imports so that `@use` comes before `@import`. Otherwise Sass will throw an error. |
| 143 | + if (headerStartIndex > -1 && headerEndIndex > -1) { |
| 144 | + const headers = lines |
| 145 | + .splice(headerStartIndex, headerEndIndex - headerStartIndex) |
| 146 | + .sort((a, b) => a.startsWith('@use') && !b.startsWith('@use') ? -1 : 0); |
| 147 | + lines.splice(headerStartIndex, 0, ...headers); |
| 148 | + } |
| 149 | + |
| 150 | + fs.writeFileSync(file, lines.join('\n')); |
| 151 | + }); |
| 152 | +} |
| 153 | + |
| 154 | +function clearUnwatedFiles() { |
| 155 | + // The migration script generates .import files even if we don't pass in the `--forward` when |
| 156 | + // a file has top-level variables matching a prefix. Since we still want such files to be |
| 157 | + // migrated, we clear the unwanted files afterwards. |
| 158 | + const files = glob('**/*.import.scss', {cwd: directory, absolute: true, ignore: ['**/_*.scss']}); |
| 159 | + console.log(`Clearing ${files.length} unwanted files.`); |
| 160 | + files.forEach(file => fs.unlinkSync(file)); |
| 161 | +} |
0 commit comments