freezer.ts
1 import { spawn } from 'child_process'; 2 import * as path from 'path'; 3 import * as fs from 'fs'; 4 5 const PROJECT_BASE = path.resolve('./'); 6 const ORACLE_BRANCH = 'develop'; 7 const GET_PACKAGE_CMD = `git show ${ORACLE_BRANCH}:package.json`; 8 const GET_DIFF_CMD = `git diff origin/${ORACLE_BRANCH}`; 9 10 const newFileRegEx = /^\+\+\+ b\//; 11 const frozenFolderRegEx = /\/\*$/; 12 13 const start = async () => { 14 try { 15 const packageStr = await runShCommand(GET_PACKAGE_CMD); 16 const diff = await runShCommand(GET_DIFF_CMD); 17 const { frozen } = JSON.parse(packageStr); 18 19 if (frozen === undefined) { 20 console.log(`Freezer: No config found in package.json on branch ${ORACLE_BRANCH}. Exiting.`); 21 return; 22 } 23 24 const newFiles = getNewFiles(diff); 25 const frozenFiles = getFrozenFiles(frozen); 26 const frozenFolders = getFrozenFolders(frozen); 27 28 ensureNewFilesAreNotFrozen(newFiles, frozenFiles, frozenFolders); 29 } catch (err) { 30 console.log(err.message); 31 exit(); 32 } 33 }; 34 35 const ensureNewFilesAreNotFrozen = ( 36 newFiles: string[], 37 frozenFiles: string[], 38 frozenFolders: string[] 39 ): void => { 40 const errors = newFiles 41 .map(file => { 42 if (frozenFiles.indexOf(file) !== -1) { 43 return `"${file}" is frozen`; 44 } 45 if (isFileInFrozenFolders(file, frozenFolders)) { 46 return `"${file}" is in a frozen folder`; 47 } 48 }) 49 .filter(err => err); 50 51 if (errors.length) { 52 throw new Error(`Frozen files have been modified:\n${errors.join('\n')}`); 53 } else { 54 console.log('Freezer: no frozen files modified.'); 55 } 56 }; 57 58 const isFileInFrozenFolders = (file: string, folders: string[]): boolean => 59 folders.reduce((isFrozen, folder) => { 60 if (isFrozen) { 61 return isFrozen; 62 } 63 const folderSplit = folder.replace(frozenFolderRegEx, '').split('/'); 64 65 const fileSplit = file.split('/').slice(0, folderSplit.length); 66 67 return JSON.stringify(folderSplit) === JSON.stringify(fileSplit); 68 }, false); 69 70 const getFrozenFiles = (frozen: string[]): string[] => 71 frozen.filter(f => !frozenFolderRegEx.test(f)); 72 73 const getFrozenFolders = (frozen: string[]): string[] => 74 frozen.filter(f => frozenFolderRegEx.test(f)); 75 76 const getNewFiles = (diff: string): string[] => 77 diff 78 .split('\n') 79 .filter(line => newFileRegEx.test(line)) 80 .map(line => line.replace(newFileRegEx, '')); 81 82 const runShCommand = (cmd: string): Promise<string> => 83 new Promise((resolve, reject) => { 84 const sh = spawn('sh', ['-c', cmd]); 85 const stdout: string[] = []; 86 const stderr: string[] = []; 87 88 sh.stdout.on('data', data => { 89 stdout.push(data.toString()); 90 }); 91 sh.stderr.on('data', data => { 92 stderr.push(data.toString()); 93 }); 94 sh.on('close', code => { 95 if (code !== 0) { 96 console.error(stderr.join('')); 97 reject(`Child process closed with code ${code}`); 98 } 99 resolve(stdout.join('')); 100 }); 101 }); 102 103 const isTravisPushJob = () => { 104 const prb = process.env.TRAVIS_PULL_REQUEST_BRANCH; 105 return typeof prb === 'string' && prb.length === 0; 106 }; 107 108 const exit = () => setTimeout(() => process.exit(1), 100); 109 110 // check to make sure that all of the freezer config in 111 // the "frozen" property of package.json is valid 112 const validateConfig = () => { 113 try { 114 const packagePath = path.resolve(PROJECT_BASE, 'package.json'); 115 const { frozen } = JSON.parse(fs.readFileSync(packagePath, 'utf8')); 116 117 if (frozen === undefined) { 118 console.log(`Freezer: No config found in package.json on branch ${ORACLE_BRANCH}. Exiting.`); 119 return; 120 } 121 122 if (!Array.isArray(frozen)) { 123 throw new Error(`Property "frozen" is not an array`); 124 } 125 126 const errors = frozen 127 .map(filePath => { 128 const isFolder = frozenFolderRegEx.test(filePath); 129 const fullPath = isFolder 130 ? path.resolve(PROJECT_BASE, filePath.replace(frozenFolderRegEx, '')) 131 : path.resolve(PROJECT_BASE, filePath); 132 133 if (!fs.existsSync(fullPath)) { 134 return `"${filePath}" does not exist`; 135 } 136 137 const stats = fs.lstatSync(fullPath); 138 139 if (isFolder) { 140 if (!stats.isDirectory()) { 141 return `"${filePath}" is not a folder`; 142 } 143 } else { 144 if (!stats.isFile()) { 145 return `"${filePath}" is not a file`; 146 } 147 } 148 }) 149 .filter(err => err); 150 151 if (errors.length) { 152 throw new Error(errors.join('\n')); 153 } else { 154 console.log('Freezer: Config is valid.'); 155 } 156 } catch (err) { 157 console.log(`Freezer: Invalid config on package.json:\n${err.message}`); 158 exit(); 159 } 160 }; 161 162 if (isTravisPushJob()) { 163 console.log('Freezer: Travis push job detected. Exiting.'); 164 } else if (process.argv[2] === '--validate') { 165 validateConfig(); 166 } else { 167 start(); 168 }