All files shebang_check.ts

100% Statements 85/85
100% Branches 31/31
100% Functions 20/20
100% Lines 63/63

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 991x 1x                         1x 1x 1x 30x   1x 19x 19x   1x 18x 18x   1x 26x 18x 18x 18x 17x 16x 17x 17x   4x   1x 15x 12x     1x 14x 14x 10x 10x 10x 10x 10x       30x 9x 18x 12x 12x 12x     9x   9x 3x 3x 2x 2x     1x 5x 5x 1x 1x   4x 4x 4x 2x 2x     2x   4x 2x 1x   1x 1x     4x   1x  
import * as fs from 'fs';
import * as path from 'path';
 
interface File {
  location: string;
  content: string;
  hasShebang: boolean;
  basename: string;
}
 
/** checks if a file has a shebang at the top, or if provided a package.json,
 * checks that all bin provided have shebangs, throws an error if they do not
 * have it, useful for testing within CI for npm packages and reliably asserting
 * that a executable file indeed has a shebang at the top. */
export class ShebangCheck {
  static pattern = /^#!(.*)/;
  static async readFile(location: string) {
    return await fs.promises.readFile(location, 'utf-8');
  }
  static async readJson(location: string) {
    const content = await ShebangCheck.readFile(location);
    return JSON.parse(content);
  }
  static joinDirname(location: string) {
    const dirname = path.dirname(location);
    return (p: string) => path.join(dirname, p);
  }
  static async binFiles(location: string) {
    if (path.extname(location) === '.json') {
      const joinDirname = ShebangCheck.joinDirname(location);
      const json = await ShebangCheck.readJson(location);
      if (!json.bin) return [];
      if (typeof json.bin === 'string') return [joinDirname(json.bin)];
      return Object.values(json.bin)
        .map(s => (typeof s === 'string' ? joinDirname(s) : ''))
        .filter(f => f);
    }
    return [location];
  }
  static async uniqueBinFiles(location: string) {
    return (await ShebangCheck.binFiles(location)).filter(
      (value, index, self) => self.indexOf(value) === index
    );
  }
  static async files(location: string) {
    const files = await ShebangCheck.uniqueBinFiles(location);
    return await Promise.all(
      files.map(async location => {
        const content = await ShebangCheck.readFile(location);
        const hasShebang = Boolean(content.match(ShebangCheck.pattern));
        const basename = path.basename(location);
        return {location, content, hasShebang, basename};
      })
    );
  }
  static async multiFile(...locations: string[]) {
    let results: File[] = [];
    await Promise.all(
      locations.map(async location => {
        const data = await ShebangCheck.files(location);
        results = [...results, ...data];
      })
    );
    return results;
  }
  static async check(...location: string[]) {
    const files = await ShebangCheck.multiFile(...location);
    if (!files.length) throw new Error(`file ${location} is missing shebang`);
    files.forEach(({basename, hasShebang}) => {
      if (!hasShebang) throw new Error(`file ${basename} is missing shebang`);
    });
  }
  static async cli(process: NodeJS.Process) {
    const locations = process.argv.slice(2);
    if (!locations.length) {
      process.stderr.write('no file passed in\n');
      return process.exit(1);
    }
    let code = 0;
    const files = await ShebangCheck.multiFile(...locations);
    if (!files.length) {
      const noun = locations.length === 1 ? ['file', 'is'] : ['files', 'are'];
      process.stderr.write(
        `${noun[0]} ${locations.join(', ')} ${noun[1]} missing shebang\n`
      );
      code = 1;
    }
    files.forEach(({basename, hasShebang}) => {
      if (hasShebang) {
        process.stdout.write(`file ${basename} has shebang\n`);
      } else {
        process.stderr.write(`file ${basename} is missing shebang\n`);
        code = 1;
      }
    });
    process.exit(code);
  }
}