|
| 1 | +import * as yeoman from 'yeoman-generator'; |
| 2 | +import * as glob from 'glob'; |
| 3 | +import * as gitignore from 'gitignore-parser'; |
| 4 | +import * as fs from 'fs'; |
| 5 | +import * as path from 'path'; |
| 6 | +import * as _ from 'lodash'; |
| 7 | +import * as diff from 'diff'; |
| 8 | +import * as mkdirp from 'mkdirp'; |
| 9 | +import * as rimraf from 'rimraf'; |
| 10 | + |
| 11 | +const textFileExtensions = ['.gitignore', '.config', '.cs', '.cshtml', 'Dockerfile', '.html', '.js', '.json', '.jsx', '.md', '.ts', '.tsx']; |
| 12 | + |
| 13 | +const templates = { |
| 14 | + 'angular-2': '../../templates/Angular2Spa/', |
| 15 | + 'knockout': '../../templates/KnockoutSpa/', |
| 16 | + 'react-redux': '../../templates/ReactReduxSpa/', |
| 17 | + 'react': '../../templates/ReactSpa/' |
| 18 | +}; |
| 19 | + |
| 20 | +function isTextFile(filename: string): boolean { |
| 21 | + return textFileExtensions.indexOf(path.extname(filename).toLowerCase()) >= 0; |
| 22 | +} |
| 23 | + |
| 24 | +function writeFileEnsuringDirExists(root: string, filename: string, contents: string | Buffer) { |
| 25 | + let fullPath = path.join(root, filename); |
| 26 | + mkdirp.sync(path.dirname(fullPath)); |
| 27 | + fs.writeFileSync(fullPath, contents); |
| 28 | +} |
| 29 | + |
| 30 | +function listFilesExcludingGitignored(root: string): string[] { |
| 31 | + let gitignoreEvaluator = gitignore.compile(fs.readFileSync(path.join(root, '.gitignore'), 'utf8')); |
| 32 | + return glob.sync('**/*', { cwd: root, dot: true, nodir: true }) |
| 33 | + .filter(fn => gitignoreEvaluator.accepts(fn)); |
| 34 | +} |
| 35 | + |
| 36 | +function writeCommonFiles(outDir: string) { |
| 37 | + let filesByTemplate = _.mapValues(templates, listFilesExcludingGitignored); |
| 38 | + let commonFiles = _.intersection.apply(_, _.values(filesByTemplate)); |
| 39 | + |
| 40 | + commonFiles.forEach(fn => { |
| 41 | + let templateRoots = _.values(templates); |
| 42 | + let origContent = fs.readFileSync(path.join(templateRoots[0], fn)); |
| 43 | + |
| 44 | + if (isTextFile(fn)) { |
| 45 | + // For text files, we copy the portion that's common to all the templates |
| 46 | + let commonText = origContent.toString('utf8'); |
| 47 | + templateRoots.slice(1).forEach(otherTemplateRoot => { |
| 48 | + let otherTemplateContent = fs.readFileSync(path.join(otherTemplateRoot, fn), 'utf8'); |
| 49 | + commonText = diff.diffLines(commonText, otherTemplateContent) |
| 50 | + .filter(c => !(c.added || c.removed)) |
| 51 | + .map(c => c.value) |
| 52 | + .join(''); |
| 53 | + }); |
| 54 | + |
| 55 | + writeFileEnsuringDirExists(outDir, fn, commonText); |
| 56 | + } else { |
| 57 | + // For binary (or maybe-binary) files, we only consider them common if they are identical across all templates |
| 58 | + let isIdenticalEverywhere = !templateRoots.slice(1).some(otherTemplateRoot => { |
| 59 | + return !fs.readFileSync(path.join(otherTemplateRoot, fn)).equals(origContent); |
| 60 | + }); |
| 61 | + if (isIdenticalEverywhere) { |
| 62 | + writeFileEnsuringDirExists(outDir, fn, origContent); |
| 63 | + } |
| 64 | + } |
| 65 | + }); |
| 66 | +} |
| 67 | + |
| 68 | +function writeDiffsForTemplate(sourceRoot: string, destRoot: string, commonRoot: string) { |
| 69 | + listFilesExcludingGitignored(sourceRoot).forEach(fn => { |
| 70 | + const commonFn = path.join(commonRoot, fn); |
| 71 | + const sourceContent = fs.readFileSync(path.join(sourceRoot, fn)); |
| 72 | + |
| 73 | + if (!fs.existsSync(commonFn)) { |
| 74 | + // This file is unique to this template - just copy as-is |
| 75 | + writeFileEnsuringDirExists(destRoot, fn, sourceContent); |
| 76 | + } else { |
| 77 | + let commonText = fs.readFileSync(commonFn, 'utf8'); |
| 78 | + let sourceText = sourceContent.toString('utf8'); |
| 79 | + if (commonText !== sourceText) { |
| 80 | + // Write a diff vs the common version of this file |
| 81 | + let fileDiff = diff.createPatch(fn, commonText, sourceText, null, null); |
| 82 | + writeFileEnsuringDirExists(destRoot, fn + '.patch', fileDiff); |
| 83 | + } |
| 84 | + } |
| 85 | + }); |
| 86 | +} |
| 87 | + |
| 88 | +const outputRoot = './generator-aspnet-spa'; |
| 89 | +const commonRoot = path.join(outputRoot, 'templates/common'); |
| 90 | +rimraf.sync(outputRoot); |
| 91 | +writeCommonFiles(commonRoot); |
| 92 | + |
| 93 | +_.forEach(templates, (templateRootDir, templateName) => { |
| 94 | + writeDiffsForTemplate(templateRootDir, path.join(outputRoot, 'templates', templateName), commonRoot); |
| 95 | +}); |
0 commit comments