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 99 | 1x 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); } } |