diff --git a/.babelrc b/.babelrc index a699d330..02e7996f 100644 --- a/.babelrc +++ b/.babelrc @@ -1,13 +1,3 @@ { - "presets": [ - ["env", { - "targets": { - "node": "4.5" - } - }] - ], - "plugins": [ - "transform-class-properties", - "transform-object-rest-spread" - ] + "presets": ["./src/config/babelrc"] } diff --git a/.eslintrc b/.eslintrc index 3fd3cbc2..6b83b74a 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,15 +1,11 @@ { - "extends": [ - "kentcdodds", - "kentcdodds/jest", - "kentcdodds/prettier" - ], + "extends": ["./src/config/eslintrc.js"], "rules": { // stuff I haven't gotten around to updating in my config - "no-unused-vars": ["error", { "argsIgnorePattern": "^_", "varsIgnorePattern": "^ignored" }], "func-style": "off", "no-process-exit": "off", "import/no-dynamic-require": "off", + "import/no-unassigned-import": "off", // prettier does this for us "max-len": "off", diff --git a/babel.js b/babel.js new file mode 100644 index 00000000..f46b0555 --- /dev/null +++ b/babel.js @@ -0,0 +1 @@ +module.exports = require('./dist/config/babelrc') diff --git a/config.js b/config.js new file mode 100644 index 00000000..bb07926b --- /dev/null +++ b/config.js @@ -0,0 +1 @@ +module.exports = require('./dist/config') diff --git a/eslint.js b/eslint.js new file mode 100644 index 00000000..7cc6ee52 --- /dev/null +++ b/eslint.js @@ -0,0 +1 @@ +module.exports = require('./dist/config/eslintrc') diff --git a/jest.config.js b/jest.config.js index 19758b9f..a5095e80 100644 --- a/jest.config.js +++ b/jest.config.js @@ -1 +1,8 @@ -module.exports = require('./src/config/jest.config') +const {jest: jestConfig} = require('./src/config') + +module.exports = Object.assign(jestConfig, { + coverageThreshold: null, + coveragePathIgnorePatterns: jestConfig.coveragePathIgnorePatterns.concat( + '/scripts/', + ), +}) diff --git a/package.json b/package.json index 33f37cb5..8cae229d 100644 --- a/package.json +++ b/package.json @@ -12,31 +12,44 @@ "scripts": { "start": "nps", "test": "node src test", - "precommit": - "lint-staged && opt --in pre-commit --exec \"npm start validate\"" + "test:update": "node src test --updateSnapshot", + "build": "node src build", + "lint": "node src lint", + "validate": "node src validate", + "install": "node src install", + "uninstall": "node src uninstall", + "precommit": "node src precommit" }, - "files": ["dist"], + "files": ["dist", "babel", "eslint", "config"], "keywords": [], "author": "Kent C. Dodds (http://kentcdodds.com/)", "license": "MIT", "dependencies": { "all-contributors-cli": "^4.3.0", - "babel-cli": "^6.24.1", - "babel-preset-env": "^1.6.0", + "arrify": "^1.0.1", + "babel-cli": "^6.26.0", "babel-jest": "^20.0.3", + "babel-plugin-external-helpers": "^6.22.0", "babel-plugin-transform-class-properties": "^6.24.1", - "babel-plugin-transform-object-rest-spread": "^6.23.0", + "babel-plugin-transform-inline-environment-variables": "^0.2.0", + "babel-plugin-transform-object-rest-spread": "^6.26.0", + "babel-plugin-transform-react-jsx": "^6.24.1", + "babel-plugin-transform-react-remove-prop-types": "^0.4.8", + "babel-preset-env": "^1.6.0", + "babel-preset-react": "^6.24.1", "concurrently": "^3.5.0", "cross-spawn": "^5.1.0", - "eslint-config-kentcdodds": "^12.4.1", "eslint": "^4.2.0", + "eslint-config-kentcdodds": "^12.4.1", + "eslint-config-prettier": "^2.4.0", "husky": "^0.14.3", "jest": "^20.0.4", "lint-staged": "^4.0.1", - "lodash": "^4.17.4", + "lodash.merge": "^4.6.0", "opt-cli": "^1.5.1", "prettier": "^1.6.1", "read-pkg-up": "^2.0.0", + "resolve": "^1.4.0", "resolve-bin": "^0.4.0", "rimraf": "^2.6.1" }, @@ -44,19 +57,6 @@ "nps": "^5.4.0", "nps-utils": "^1.2.0" }, - "lint-staged": { - "*.+(js|json)": ["prettier --write", "git add"] - }, - "prettier": { - "printWidth": 80, - "tabWidth": 2, - "useTabs": false, - "semi": false, - "singleQuote": true, - "trailingComma": "es5", - "bracketSpacing": false, - "jsxBraketSameLine": false - }, "repository": { "type": "git", "url": "https://github.com/kentcdodds/kcd-scripts.git" diff --git a/src/config/babel-transform.js b/src/config/babel-transform.js new file mode 100644 index 00000000..cbda1e7a --- /dev/null +++ b/src/config/babel-transform.js @@ -0,0 +1,5 @@ +const babelJest = require('babel-jest') + +module.exports = babelJest.createTransformer({ + presets: [require.resolve('./babelrc')], +}) diff --git a/src/config/babelrc.js b/src/config/babelrc.js new file mode 100644 index 00000000..adf89233 --- /dev/null +++ b/src/config/babelrc.js @@ -0,0 +1,26 @@ +const {ifAnyDep} = require('../utils') + +const isTest = process.env.NODE_ENV === 'test' +const isPreact = process.env.LIBRARY === 'preact' +const isRollup = Boolean(JSON.parse(process.env.ROLLUP_BUILD || '"false"')) +const isWebpack = Boolean(JSON.parse(process.env.WEBPACK_BUILD || '"false"')) +const treeshake = isRollup || isWebpack + +module.exports = { + presets: [ + ['env', treeshake ? {modules: false} : {}], + ifAnyDep('react'), + ].filter(Boolean), + plugins: [ + isRollup ? 'external-helpers' : null, + // we're actually not using JSX at all, but I'm leaving this + // in here just in case we ever do (this would be easy to miss). + isPreact ? ['transform-react-jsx', {pragma: 'h'}] : null, + isPreact + ? ['transform-react-remove-prop-types', {removeImport: true}] + : null, + isTest || isRollup ? 'transform-inline-environment-variables' : null, + 'transform-class-properties', + 'transform-object-rest-spread', + ].filter(Boolean), +} diff --git a/src/config/eslintrc.js b/src/config/eslintrc.js index c6fdc0a1..c787bfb5 100644 --- a/src/config/eslintrc.js +++ b/src/config/eslintrc.js @@ -1,17 +1,17 @@ -const {applyOverrides, ifDevDep} = require('../utils') +const {ifAnyDep} = require('../utils') -const config = applyOverrides({ - type: 'eslint', - config: { - extends: [ - 'kentcdodds', - ifDevDep('jest', 'kentcdodds/jest'), - ifDevDep('webpack', 'kentcdodds/webpack'), - ifDevDep('react', 'kentcdodds/jsx-a11y'), - ifDevDep('react', 'kentcdodds/react'), - ifDevDep('prettier', 'kentcdodds/prettier'), - ].filter(Boolean), - rules: { +module.exports = { + extends: [ + 'kentcdodds', + ifAnyDep('jest', 'kentcdodds/jest'), + ifAnyDep('webpack', 'kentcdodds/webpack'), + ifAnyDep('react', 'kentcdodds/jsx-a11y'), + ifAnyDep('react', 'kentcdodds/react'), + 'prettier', + ifAnyDep('prettier', 'kentcdodds/prettier'), + ].filter(Boolean), + rules: Object.assign( + { // stuff I haven't gotten around to updating in my config 'no-unused-vars': [ 'error', @@ -21,21 +21,19 @@ const config = applyOverrides({ 'no-process-exit': 'off', // prettier does this for us - ...ifDevDep( - 'prettier', - { - 'max-len': 'off', - semi: 'off', - quotes: 'off', - 'comma-dangle': 'off', - 'no-console': 'off', - indent: 'off', - 'babel/object-curly-spacing': 'off', - }, - {} - ), }, - }, -}) - -module.exports = config + ifAnyDep( + 'prettier', + { + 'max-len': 'off', + semi: 'off', + quotes: 'off', + 'comma-dangle': 'off', + 'no-console': 'off', + indent: 'off', + 'babel/object-curly-spacing': 'off', + }, + {}, + ), + ), +} diff --git a/src/config/index.js b/src/config/index.js new file mode 100644 index 00000000..a733fb9d --- /dev/null +++ b/src/config/index.js @@ -0,0 +1,7 @@ +module.exports = { + babel: require('./babelrc'), + eslint: require('./eslintrc'), + jest: require('./jest.config'), + lintStaged: require('./lintstagedrc'), + prettier: require('./prettierrc'), +} diff --git a/src/config/jest.config.js b/src/config/jest.config.js index 3e256282..a943a27e 100644 --- a/src/config/jest.config.js +++ b/src/config/jest.config.js @@ -1,4 +1,7 @@ -const {ifDevDep, applyOverrides} = require('../utils') +const path = require('path') +const {ifDevDep} = require('../utils') + +const here = p => path.join(__dirname, p) const ignores = [ '/node_modules/', @@ -7,23 +10,22 @@ const ignores = [ '__mocks__', ] -const config = applyOverrides({ - type: 'jest', - config: { - testEnvironment: ifDevDep('webpack', 'jsdom', 'node'), - collectCoverageFrom: ['src/**/*.js'], - testMatch: ['**/__tests__/**/*.js'], - testPathIgnorePatterns: ignores, - coveragePathIgnorePatterns: ignores, - coverageThreshold: { - global: { - branches: 100, - functions: 100, - lines: 100, - statements: 100, - }, +module.exports = { + testEnvironment: ifDevDep(['webpack', 'rollup'], 'jsdom', 'node'), + collectCoverageFrom: ['src/**/*.js'], + testMatch: ['**/__tests__/**/*.js'], + testPathIgnorePatterns: ignores, + coveragePathIgnorePatterns: ignores, + transform: { + '^.+\\.js$': here('./babel-transform'), + }, + transformIgnorePatterns: ['[/\\\\]node_modules[/\\\\].+\\.(js|jsx)$'], + coverageThreshold: { + global: { + branches: 100, + functions: 100, + lines: 100, + statements: 100, }, }, -}) - -module.exports = config +} diff --git a/src/config/lintstagedrc.js b/src/config/lintstagedrc.js new file mode 100644 index 00000000..3ffa8020 --- /dev/null +++ b/src/config/lintstagedrc.js @@ -0,0 +1,10 @@ +const [executor] = process.argv + +module.exports = { + linters: { + '**/*.+(js|json|less|css|ts)': [ + `${executor} ${require.resolve('../')} format`, + 'git add', + ], + }, +} diff --git a/src/config/prettierignore b/src/config/prettierignore new file mode 100644 index 00000000..5ab667c9 --- /dev/null +++ b/src/config/prettierignore @@ -0,0 +1,6 @@ +node_modules/ +coverage/ +dist/ +build/ +out/ +.next/ diff --git a/src/config/prettierrc.js b/src/config/prettierrc.js index d487c288..363bd9b9 100644 --- a/src/config/prettierrc.js +++ b/src/config/prettierrc.js @@ -1,17 +1,10 @@ -const {applyOverrides} = require('../utils') - -const config = applyOverrides({ - type: 'prettier', - config: { - printWidth: 80, - tabWidth: 2, - useTabs: false, - semi: false, - singleQuote: true, - trailingComma: 'all', - bracketSpacing: false, - jsxBraketSameLine: false, - }, -}) - -module.exports = config +module.exports = { + printWidth: 80, + tabWidth: 2, + useTabs: false, + semi: false, + singleQuote: true, + trailingComma: 'all', + bracketSpacing: false, + jsxBraketSameLine: false, +} diff --git a/src/scripts/build.js b/src/scripts/build.js index 2d89b4fb..8c5e3654 100644 --- a/src/scripts/build.js +++ b/src/scripts/build.js @@ -16,7 +16,7 @@ const result = spawn.sync( '--ignore', '__tests__,__mocks__', 'src', ].concat(argv), - {stdio: 'inherit'} + {stdio: 'inherit'}, ) -process.exit(result) +process.exit(result.status) diff --git a/src/scripts/format.js b/src/scripts/format.js index 224c97f7..2221ad43 100644 --- a/src/scripts/format.js +++ b/src/scripts/format.js @@ -1,20 +1,34 @@ +const fs = require('fs') const path = require('path') const resolveBin = require('resolve-bin') const spawn = require('cross-spawn') +const {fromRoot} = require('../paths') +const {hasPkgProp} = require('../utils') -const argv = process.argv.slice(2) +const args = process.argv.slice(2) const here = p => path.join(__dirname, p) +const useBuiltinConfig = + !args.includes('--config') && + !fs.existsSync(fromRoot('.prettierrc')) && + !hasPkgProp('prettierrc') +const config = useBuiltinConfig + ? ['--config', here('../config/prettierrc.js')] + : [] + +const useBuiltinIgnore = + !args.includes('--ignore-path') && !fs.existsSync(fromRoot('.prettierignore')) +const ignore = useBuiltinIgnore + ? ['--ignore-path', here('../config/prettierignore')] + : [] + +const write = args.includes('--no-write') ? [] : ['--write'] + const result = spawn.sync( resolveBin.sync('prettier'), - // prettier-ignore - [ - '--write', - '--config', here('../config/prettierrc.js'), - '--ignore-path', here('../config/eslintignore'), - ].concat(argv), - {stdio: 'inherit'} + [...config, ...ignore, ...write].concat(args), + {stdio: 'inherit'}, ) -process.exit(result) +process.exit(result.status) diff --git a/src/scripts/install.js b/src/scripts/install.js new file mode 100644 index 00000000..393fa981 --- /dev/null +++ b/src/scripts/install.js @@ -0,0 +1 @@ +require('husky/bin/install') diff --git a/src/scripts/lint.js b/src/scripts/lint.js index 7d8505b9..8ddc0964 100644 --- a/src/scripts/lint.js +++ b/src/scripts/lint.js @@ -1,21 +1,33 @@ +const fs = require('fs') const path = require('path') const resolveBin = require('resolve-bin') const spawn = require('cross-spawn') +const {fromRoot} = require('../paths') +const {hasPkgProp} = require('../utils') const [ignoredExecutor, ignoredBin, ignoredScript, ...args] = process.argv - const here = p => path.join(__dirname, p) +const useBuiltinConfig = + !args.includes('--config') && + !fs.existsSync(fromRoot('.eslintrc')) && + !hasPkgProp('eslint') +const config = useBuiltinConfig + ? ['--config', here('../config/eslintrc.js')] + : [] + +const useBuiltinIgnore = + !args.includes('--ignore-path') && !fs.existsSync(fromRoot('.eslintignore')) +const ignore = useBuiltinIgnore + ? ['--ignore-path', here('../config/eslintignore')] + : [] + +const cache = args.includes('--no-cache') ? [] : ['--cache'] + const result = spawn.sync( resolveBin.sync('eslint'), - // prettier-ignore - [ - '--config', here('../config/eslintrc.js'), - '--ignore-path', here('../config/eslintignore'), - '--cache', - '.', - ].concat(args), - {stdio: 'inherit'} + [...config, ...ignore, ...cache, '.'].concat(args), + {stdio: 'inherit'}, ) if (result.status === 0) { diff --git a/src/scripts/precommit/index.js b/src/scripts/precommit/index.js new file mode 100644 index 00000000..733f0f67 --- /dev/null +++ b/src/scripts/precommit/index.js @@ -0,0 +1,21 @@ +const spawn = require('cross-spawn') + +const [executor, ...args] = process.argv + +const lintStagedResult = spawn.sync( + executor, + [require.resolve('./lint-staged')].concat(args), + {stdio: 'inherit'}, +) + +if (lintStagedResult.status !== 0) { + process.exit(lintStagedResult.status) +} + +const validateResult = spawn.sync( + executor, + [require.resolve('../../'), 'validate'].concat(args), + {stdio: 'inherit'}, +) + +process.exit(validateResult.status) diff --git a/src/scripts/precommit/lint-staged.js b/src/scripts/precommit/lint-staged.js new file mode 100644 index 00000000..02a68ea7 --- /dev/null +++ b/src/scripts/precommit/lint-staged.js @@ -0,0 +1,37 @@ +const fs = require('fs') +const path = require('path') +const resolve = require('resolve') +const {fromRoot} = require('../../paths') +const {hasPkgProp} = require('../../utils') + +const useBuiltinConfig = + !fs.existsSync(fromRoot('.lintstagedrc')) && + !fs.existsSync(fromRoot('lint-staged.config.js')) && + !hasPkgProp('lint-staged') + +const lintStagedPath = require.resolve('lint-staged') +const cosmiconfigPath = resolve.sync('cosmiconfig', { + basedir: path.dirname(lintStagedPath), +}) + +// lint-staged uses cosmiconfig to find its configuration +// and it has no other way to provide config +// (via a node API or command-line flag) +// So, we're doing this require cache magic to provide our own +// config so folks don't have to have that in their package.json +// +// In addition, folks can override things with lint-staged just like +// they do with everything else using `paypal.config.js` +function fakeCosmiconfig(...args) { + if (args[0] === 'lint-staged') { + return Promise.resolve({config: require('../../config/lintstagedrc')}) + } else { + return require(cosmiconfigPath)(...args) + } +} + +if (useBuiltinConfig) { + require.cache[cosmiconfigPath] = {exports: fakeCosmiconfig} +} + +require(lintStagedPath) diff --git a/src/scripts/test.js b/src/scripts/test.js index b9c1988b..c7a8698b 100644 --- a/src/scripts/test.js +++ b/src/scripts/test.js @@ -1,13 +1,28 @@ +/* istanbul ignore next */ process.env.BABEL_ENV = 'test' process.env.NODE_ENV = 'test' +const fs = require('fs') const jest = require('jest') +const {hasPkgProp} = require('../utils') +const {fromRoot} = require('../paths') -const argv = process.argv.slice(2) +const args = process.argv.slice(2) -if (!process.env.CI && !argv.includes('--coverage')) { - argv.push('--watch') -} -argv.push('--config', JSON.stringify(require('../config/jest.config'))) +const watch = + !process.env.CI && + !args.includes('--coverage') && + !args.includes('--updateSnapshot') + ? ['--watch'] + : [] -jest.run(argv) +const useBuiltinConfig = + !args.includes('--config') && + !fs.existsSync(fromRoot('jest.config.js')) && + !hasPkgProp('jest') + +const config = useBuiltinConfig + ? ['--config', JSON.stringify(require('../config/jest.config'))] + : [] + +jest.run([...config, ...watch, ...args]) diff --git a/src/scripts/uninstall.js b/src/scripts/uninstall.js new file mode 100644 index 00000000..a0fcd915 --- /dev/null +++ b/src/scripts/uninstall.js @@ -0,0 +1 @@ +require('husky/bin/uninstall') diff --git a/src/scripts/validate.js b/src/scripts/validate.js index 86e8c45f..be1baf3e 100644 --- a/src/scripts/validate.js +++ b/src/scripts/validate.js @@ -1,4 +1,5 @@ const spawn = require('cross-spawn') +const resolveBin = require('resolve-bin') const {ifScript} = require('../utils') const scriptNames = (...scripts) => @@ -8,20 +9,20 @@ const scriptNames = (...scripts) => .join(',') const result = spawn.sync( - require.resolve('concurrently'), + resolveBin.sync('concurrently'), // prettier-ignore [ '--kill-others-on-fail', '--prefix', '[{name}]', '--names', scriptNames('build', 'lint', 'test'), - '--prefix-colors', 'bgBlue.bold,bgGreen.bold,bgOrang.bold', + '--prefix-colors', 'bgBlue.bold,bgGreen.bold,bgMagenta.bold', ...[ ifScript('build', 'npm run build --silent'), ifScript('lint', 'npm run lint --silent'), ifScript('test', 'npm run test --silent -- --coverage'), ].filter(Boolean), ], - {stdio: 'inherit'} + {stdio: 'inherit'}, ) -process.exit(result) +process.exit(result.status) diff --git a/src/utils.js b/src/utils.js index ffdeb023..f054a4ce 100644 --- a/src/utils.js +++ b/src/utils.js @@ -1,90 +1,42 @@ const fs = require('fs') const path = require('path') -const {mergeWith} = require('lodash') const readPkgUp = require('read-pkg-up') +const arrify = require('arrify') -function hasDevDependency(dep) { - return getPkgProp('devDependencies').hasOwnProperty(dep) -} - -function getPkgProp(prop) { - const {pkg} = readPkgUp.sync({cwd: process.cwd()}) - return pkg[prop] -} - -function hasScript(script) { - return getPkgProp('scripts').hasOwnProperty(script) -} - -function ifScript(script, t, f) { - return hasScript(script) ? t : f -} +const getPkgProp = prop => readPkgUp.sync({cwd: process.cwd()}).pkg[prop] +const hasPkgProp = (pkgProp, props) => + arrify(props).some(prop => (getPkgProp(pkgProp) || {}).hasOwnProperty(prop)) +const ifPkgProp = (pkgProp, props, t, f) => (hasPkgProp(pkgProp, props) ? t : f) -function ifDevDep(dep, t, f) { - return hasDevDependency(dep) ? t : f -} +const hasPeerDep = hasPkgProp.bind(null, 'peerDependencies') +const hasDep = hasPkgProp.bind(null, 'dependencies') +const hasDevDep = hasPkgProp.bind(null, 'devDependencies') +const hasAnyDep = (...args) => + [hasDep, hasDevDep, hasPeerDep].some(fn => fn(...args)) -// this will do a deep merge of two objects -// If the object has a value that's an array, -// then the source's value will be concat-ed with it. -function mergeWithArrayConcat(object, source) { - return mergeWith(object, source, (objValue, srcValue) => { - if (Array.isArray(objValue)) { - return objValue.concat(srcValue) - } - return srcValue - }) -} +const ifPeerDep = ifPkgProp.bind(null, 'peerDependencies') +const ifDep = ifPkgProp.bind(null, 'dependencies') +const ifDevDep = ifPkgProp.bind(null, 'devDependencies') +const ifAnyDep = (deps, t, f) => (hasAnyDep(deps) ? t : f) -function applyOverrides({config = {}, type}) { - config = applyOverridesToPath({ - config, - type, - overrideConfig: readUserConfig()[type], - }) - return config -} +const ifScript = ifPkgProp.bind(null, 'scripts') -function readUserConfig() { +function ifConfig(type, defaultConfig) { const appDirectory = fs.realpathSync(process.cwd()) const configPath = path.resolve(path.join(appDirectory, '/kcd.config.js')) if (fs.existsSync(configPath)) { - return require(configPath) + return require(configPath)[type] || defaultConfig } else { return {} } } -// eslint-disable-next-line complexity -function applyOverridesToPath({config, type, overrideConfig}) { - if (!overrideConfig) { - return config - } - try { - if (typeof overrideConfig === 'function') { - const overrides = overrideConfig(config, process.env.NODE_ENV) - if (!overrides) { - throw new Error( - `${type} overrides function provided, but the config was not returned. You need to return the config` - ) - } - return overrides - } else if (typeof overrideConfig === 'object') { - return mergeWithArrayConcat(config, overrideConfig) - } - } catch (error) { - console.error( - `There was a problem trying to apply ${type} config overrides` - ) - throw error - } - return config -} - module.exports = { - applyOverrides, - hasDevDependency, ifDevDep, - hasScript, + ifPeerDep, ifScript, + ifDep, + ifAnyDep, + ifConfig, + hasPkgProp, }