/ common / freezer.ts
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  }