#!/usr/bin/env node const { config } = require('../package.json'); const { exists } = require('mz/fs'); const sgf = require('staged-git-files'); const forceArray = require('force-array'); const awaitify = require('apr-awaitify'); const asyncFilter = require('apr-filter'); const map = require('apr-map'); const reduce = require('apr-reduce'); const parallel = require('apr-parallel'); const execa = require('execa'); const globby = require('globby'); const main = require('apr-main'); const argv = require('yargs').argv; const path = require('path'); const checksum = require('checksum'); const getStaged = awaitify(sgf); const asyncChecksum = awaitify(checksum.file); const ROOT = path.join(__dirname, '../'); const SCRIPTS = path.resolve(__dirname); const optOut = forceArray(config['fmt-opt-out']).map(pkg => path.join(ROOT, `packages/${pkg}`) ); const statuses = ['Added', 'Modified']; const filter = (files = []) => files .filter(file => !/node_modules|dist/.test(file)) .map(file => path.resolve(ROOT, file)) .filter(file => !optOut.some(pkg => file.indexOf(pkg) === 0)); const run = async (files = []) => { const filteredFiles = filter(files); const _files = filteredFiles.reduce( (files, file) => { const ext = path.extname(file).replace(/^./, '') || 'js'; return Object.assign(files, { [ext]: (files[ext] || 'js').concat(file) }); }, { js: [], gql: [], json: [] } ); return parallel({ js: () => _files.js.length ? execa( 'prettier', ['--write', '--single-quote', '--parser=babylon'].concat(_files.js), { stdio: 'inherit' } ) : null, gql: () => _files.gql.length ? execa( 'prettier', ['--write', '--single-quote', '--parser=graphql'].concat( _files.gql ), { stdio: 'inherit' } ) : null, json: () => _files.json.length ? execa( 'prettier', ['--write', '--single-quote', '--parser=json'].concat(_files.json), { stdio: 'inherit' } ) : null }); }; const add = async filename => execa('git', ['add', filename]); const all = async () => { const files = await globby(['packages/**/*.{js,gql,json}', 'scripts/*'], { cwd: path.join(__dirname, '..') }); return run(files); }; const getUnstaged = async () => { const unstaged = await execa('git', ['ls-files', '-m']); return unstaged.stdout.split('\n'); }; const staged = async () => { const unstaged = (await getUnstaged()) .map(file => path.resolve(ROOT, file)) .filter(file => /\.js|gql|json$/.test(file) || file.indexOf(SCRIPTS) === 0); const files = (await getStaged()) .filter(({ status }) => statuses.indexOf(status) >= 0) .map(file => Object.assign({}, file, { filename: path.resolve(ROOT, file.filename) }) ) .filter( file => /\.js|gql|json$/.test(file.filename) || file.filename.indexOf(SCRIPTS) === 0 ); const existing = await asyncFilter( files, async ({ filename }) => await exists(filename) ); if (!existing.length) { return; } const checksums = await map(existing, async file => { const checksum = await asyncChecksum(file.filename); return Object.assign({}, file, { checksum }); }); const filenames = existing.map(file => file.filename); await run(filenames); const changed = await asyncFilter( checksums, async ({ filename, checksum }) => { const newChecksum = await asyncChecksum(filename); return checksum != newChecksum; } ); const modifieds = await reduce( changed, async (modifieds, file) => { const isUnstaged = unstaged.filter(f => f === file.filename).length; if ( (file.status === 'Modified' || file.status === 'Added') && isUnstaged ) { modifieds.push(file); } else { await add(file.filename); } return modifieds; }, [] ); if (modifieds.length) { modifieds.forEach(modified => console.log('PARTIALLY STAGED FILE ', modified.filename) ); process.exit(1); } }; main(argv._.length ? run(argv._) : argv.staged ? staged() : all());