├── .eslintignore ├── .eslintrc ├── .gitignore ├── .npmrc ├── .prettierignore ├── .prettierrc.js ├── .travis.yml ├── .vscode └── settings.json ├── LICENSE ├── README.md ├── babel.js ├── config.js ├── eslint.js ├── jest.config.js ├── jest.js ├── package-lock.json ├── package.json ├── prettier.js └── src ├── cli.js ├── config ├── babel-transform.js ├── babelrc.js ├── eslintignore ├── eslintrc.js ├── index.js ├── jest.config.js ├── lintstagedrc.js ├── prettierignore ├── prettierrc.js └── rollup.config.js ├── exec └── typescript.js ├── index.js ├── scripts ├── build │ ├── babel.js │ ├── clean.js │ ├── index.js │ └── rollup.js ├── bundle.js ├── compile │ ├── babel.js │ └── typescript.js ├── contributors.js ├── format.js ├── lint.js ├── new.js ├── pre-commit.js ├── prestart.js ├── release.js ├── setup │ ├── babel.js │ └── eslint.js ├── test.js ├── typecheck.js └── validate.js ├── templates ├── .babelrc ├── .eslintignore ├── .gitignore ├── .npmignore ├── .prettierignore ├── .travis.yml ├── LICENSE ├── README.md ├── jest.config.js └── package.json └── utils.js /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | coverage 3 | dist 4 | src/templates -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./src/config/eslintrc.js" 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | coverage 3 | dist 4 | .opt-in 5 | .opt-out 6 | .DS_Store 7 | .eslintcache 8 | 9 | # these cause more harm than good 10 | # when working with contributors 11 | # package-lock.json 12 | yarn.lock 13 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | registry=http://registry.npmjs.org/ 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | package.json 2 | package-lock.json 3 | node_modules 4 | dist 5 | coverage 6 | src/templates 7 | src/templates/.pretterrc.js -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./src/config/prettierrc'); 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 'lts/*' 4 | 5 | cache: 6 | directories: 7 | - node_modules 8 | 9 | install: 10 | - npm install 11 | 12 | script: 13 | - npm run test 14 | 15 | branches: 16 | only: 17 | - master 18 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.formatOnSave": true 3 | } 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2019 Jon Quach 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 📦 Zero 2 | 3 | [![Build Status](https://travis-ci.org/itsjonq/zero.svg?branch=master)](https://travis-ci.org/itsjonq/zero) 4 | 5 | > A zero config scripts library 6 | 7 | Zero is a ["zero config"](https://www.google.com/search?ei=eGJ7XPqGG5K_jgS2wYKoCA&q=javascript+zero+config&oq=javascript+zero+config&gs_l=psy-ab.3..0i22i30l2.2204.6555..6634...4.0..0.88.1939.29......0....1..gws-wiz.......0i71j0i131j0j0i67.eDv8lllu1MY) tool designed to make it easy to create, develop, test, build, and publish libraries. 8 | 9 | It comes with a bunch of modern front-end tools, like Babel, Rollup, ESLint, Prettier, and Jest - All pre-configured to let you build stuff without fiddling with configuration files, scripts and commands. 10 | 11 | ``` 12 | 📦 Zero 13 | 14 | zero 15 | 16 | Example: 17 | zero build 18 | 19 | Options: 20 | -V, --version output the version number 21 | -h, --help output usage information 22 | 23 | Commands: 24 | build [options] Builds project with Babel, Rollup, or TypeScript 25 | bundle [options] Bundles project into single files with Rollup 26 | contributors Generates markdown file with all contributors 27 | format [options] Formats files with Prettier 28 | lint [options] Lints files with ESLint 29 | new Generate a new module 30 | pre-commit Lints files before staging for commit 31 | prestart Automatically install dependencies before starting 32 | release Publish to npm 33 | setup [options] Sets up tooling in project 34 | test [options] Run test with Jest 35 | typecheck Check types with TypeScript 36 | validate Validates project with lint, tests, and build 37 | ``` 38 | 39 | ## Table of Contents 40 | 41 | 42 | 43 | 44 | - [Installation](#installation) 45 | - [Usage](#usage) 46 | - [CLI](#cli) 47 | - [Extending](#extending) 48 | - [Babel](#babel) 49 | - [ESlint](#eslint) 50 | - [Jest](#jest) 51 | - [Prettier](#prettier) 52 | - [Thanks](#thanks) 53 | 54 | 55 | 56 | ## Installation 57 | 58 | Add Zero to your project with this command: 59 | 60 | ``` 61 | npm install --save-dev @itsjonq/zero 62 | ``` 63 | 64 | Or globally with: 65 | 66 | ``` 67 | npm install -g @itsjonq/zero 68 | ``` 69 | 70 | ## Usage 71 | 72 | Zero comes with a handful of scripts that you can add to your own `package.json` scripts: 73 | 74 | ```json 75 | "scripts": { 76 | "prestart": "zero prestart", 77 | "build": "zero build", 78 | "format": "zero format", 79 | "lint": "zero lint", 80 | "precommit": "zero pre-commit", 81 | "release": "zero release", 82 | "test": "zero test", 83 | "validate": "zero validate", 84 | } 85 | ``` 86 | 87 | ### CLI 88 | 89 | To use Zero as a CLI, install it globally, then run this command: 90 | 91 | ``` 92 | zero 93 | ``` 94 | 95 | Alternatively, you can run it with [npx](https://medium.com/@maybekatz/introducing-npx-an-npm-package-runner-55f7d4bd282b) 96 | 97 | ``` 98 | npx @itsjonq/zero 99 | ``` 100 | 101 | ## Extending 102 | 103 | Zero can build, lint, format, and release out-of-the-box! 104 | 105 | If you need to personalize Babel, ESLint, or Jest, Zero's got you covered. Add your own adjustments by extending Zero's based configurations. 106 | 107 | ### Babel 108 | 109 | Create a `babel` or `.babelrc` file with: 110 | 111 | ``` 112 | {"presets": ["@itsjonq/zero/babel"]} 113 | ``` 114 | 115 | #### `babel-core@7` 116 | 117 | As of version `1.0.0`, Zero is now on `@babel` version 7. Your project may need to install `babel-core@7.0.0-bridge.0`. To do so, add that package to your `package.json`, or run: 118 | 119 | ``` 120 | npm install --save-dev babel-core@7.0.0-bridge.0 121 | ``` 122 | 123 | #### `@babel/runtime` 124 | 125 | Zero **does not use** `@babel/runtime`, as it is still being used to compile projects on Babel 6. If you need an ultra-modern Babel 7 ready tool, check out [kcd-scripts](https://github.com/kentcdodds/kcd-scripts). 126 | 127 | #### `babel-plugin-react-app` 128 | 129 | Zero no longer comes with `babel-plugin-react-app`. The reason is because this module uses `@babel/runtime` with the new Babel 7 set up. If your project requires `babel-plugin-react-app` (e.g. building [Docz](https://www.docz.site/)), you'll need to add it yourself as a `devDependencies`. 130 | 131 | ### ESlint 132 | 133 | Create an `.eslintrc` file with: 134 | 135 | ``` 136 | {"extends": "./node_modules/@itsjonq/zero/eslint.js"} 137 | ``` 138 | 139 | > Note: for now, you'll have to include an `.eslintignore` in your project until 140 | > [this eslint issue is resolved](https://github.com/eslint/eslint/issues/9227). 141 | 142 | ### Jest 143 | 144 | Create a `jest.config.js` file with: 145 | 146 | ```javascript 147 | const jestConfig = require('@itsjonq/zero/jest'); 148 | 149 | module.exports = Object.assign(jestConfig, { 150 | // your overrides here 151 | }); 152 | ``` 153 | 154 | ### Prettier 155 | 156 | Create a `.prettierrc.js` file with: 157 | 158 | ``` 159 | module.exports = require("@itsjonq/zero/prettier"); 160 | ``` 161 | 162 | ## Thanks 163 | 164 | Thanks to [kcd-scripts](https://github.com/kentcdodds/kcd-scripts) and [create-react-app](https://github.com/facebook/create-react-app) for the inspiration and code! 165 | -------------------------------------------------------------------------------- /babel.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./src/config/babelrc'); 2 | -------------------------------------------------------------------------------- /config.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./src/config'); 2 | -------------------------------------------------------------------------------- /eslint.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./src/config/eslintrc'); 2 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | const { jest: jestConfig } = require('./src/config'); 2 | 3 | module.exports = Object.assign(jestConfig, { 4 | coverageThreshold: null, 5 | testPathIgnorePatterns: [ 6 | ...jestConfig.testPathIgnorePatterns, 7 | 'src/scripts/test.js', 8 | ], 9 | }); 10 | -------------------------------------------------------------------------------- /jest.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./src/config/jest.config'); 2 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@itsjonq/zero", 3 | "version": "5.0.2", 4 | "public": true, 5 | "description": "A zero config scripts library", 6 | "main": "src/index.js", 7 | "bin": { 8 | "zero": "src/index.js" 9 | }, 10 | "engines": { 11 | "node": ">= 8", 12 | "npm": ">= 5" 13 | }, 14 | "scripts": { 15 | "add-contributor": "node src contributors add", 16 | "test": "CI=true npm run build", 17 | "build": "node src build", 18 | "lint": "node src lint", 19 | "format": "node src format", 20 | "validate": "node src validate", 21 | "release": "node src release" 22 | }, 23 | "files": [ 24 | "src", 25 | "babel.js", 26 | "eslint.js", 27 | "config.js", 28 | "prettier.js", 29 | "jest.js" 30 | ], 31 | "author": "Jon Quach (https://jonquach.com)", 32 | "repository": { 33 | "type": "git", 34 | "url": "git+https://github.com/itsjonq/zero.git" 35 | }, 36 | "bugs": { 37 | "url": "https://github.com/itsjonq/zero/issues" 38 | }, 39 | "homepage": "https://github.com/itsjonq/zero#readme", 40 | "license": "MIT", 41 | "dependencies": { 42 | "@babel/cli": "7.10.5", 43 | "@babel/core": "7.10.5", 44 | "@babel/plugin-proposal-class-properties": "7.10.4", 45 | "@babel/plugin-proposal-decorators": "7.10.5", 46 | "@babel/plugin-proposal-object-rest-spread": "7.10.4", 47 | "@babel/plugin-proposal-optional-chaining": "7.10.4", 48 | "@babel/plugin-syntax-dynamic-import": "7.8.3", 49 | "@babel/plugin-transform-classes": "7.10.4", 50 | "@babel/plugin-transform-destructuring": "7.10.4", 51 | "@babel/plugin-transform-flow-strip-types": "7.10.4", 52 | "@babel/plugin-transform-modules-commonjs": "7.10.4", 53 | "@babel/plugin-transform-react-constant-elements": "7.10.4", 54 | "@babel/plugin-transform-react-display-name": "7.10.4", 55 | "@babel/plugin-transform-runtime": "7.10.5", 56 | "@babel/preset-env": "7.10.4", 57 | "@babel/preset-flow": "7.10.4", 58 | "@babel/preset-react": "7.10.4", 59 | "@babel/preset-typescript": "7.10.4", 60 | "@babel/runtime": "7.10.5", 61 | "@itsjonq/prestart": "^1.0.1", 62 | "@rollup/plugin-babel": "^5.0.2", 63 | "@rollup/plugin-commonjs": "^12.0.0", 64 | "@rollup/plugin-json": "^4.0.3", 65 | "@rollup/plugin-node-resolve": "^8.0.0", 66 | "@rollup/plugin-replace": "^2.3.2", 67 | "@typescript-eslint/eslint-plugin": "2.34.0", 68 | "@typescript-eslint/parser": "2.34.0", 69 | "all-contributors-cli": "6.16.1", 70 | "arrify": "2.0.1", 71 | "babel-core": "7.0.0-bridge.0", 72 | "babel-eslint": "10.1.0", 73 | "babel-jest": "26.1.0", 74 | "babel-plugin-emotion": "10.0.33", 75 | "babel-plugin-inline-svg": "^1.0.1", 76 | "babel-plugin-macros": "2.8.0", 77 | "babel-plugin-minify-dead-code-elimination": "^0.5.1", 78 | "babel-plugin-module-resolver": "^3.2.0", 79 | "babel-plugin-transform-inline-environment-variables": "^0.4.3", 80 | "babel-plugin-transform-react-remove-prop-types": "^0.4.24", 81 | "browserslist": "4.13.0", 82 | "commander": "2.19.0", 83 | "concurrently": "5.2.0", 84 | "cross-env": "^6.0.3", 85 | "cross-spawn": "6.0.5", 86 | "doctoc": "1.4.0", 87 | "eslint": "6.8.0", 88 | "eslint-config-kentcdodds": "14.6.1", 89 | "eslint-config-prettier": "^6.11.0", 90 | "eslint-config-react-app": "5.2.1", 91 | "eslint-plugin-better-styled-components": "1.1.2", 92 | "eslint-plugin-flowtype": "^3.x", 93 | "eslint-plugin-import": "2.22.0", 94 | "eslint-plugin-jsx-a11y": "6.3.1", 95 | "eslint-plugin-react": "7.20.3", 96 | "eslint-plugin-react-hooks": "^1.x", 97 | "eslint-plugin-simple-import-sort": "5.0.3", 98 | "eslint-plugin-sort-destructure-keys": "1.3.5", 99 | "eslint-plugin-sort-keys-fix": "1.1.1", 100 | "fast-glob": "3.2.4", 101 | "husky": "^3.1.0", 102 | "inquirer": "7.3.2", 103 | "is-ci": "^2.0.0", 104 | "jest": "^26.0.1", 105 | "jest-watch-typeahead": "^0.6.0", 106 | "lint-staged": "^9.5.0", 107 | "lodash.camelcase": "^4.3.0", 108 | "lodash.has": "^4.5.2", 109 | "lodash.omit": "^4.5.0", 110 | "lodash.template": ">=4.5.0", 111 | "mkdirp": "^0.5.1", 112 | "np": "6.3.2", 113 | "prettier": "^2.0.5", 114 | "react-app-polyfill": "1.0.4", 115 | "read-pkg-up": "^6.0.0", 116 | "resolve": "1.17.0", 117 | "rimraf": "2.7.1", 118 | "rollup": "^2.10.9", 119 | "rollup-plugin-node-builtins": "^2.1.2", 120 | "rollup-plugin-node-globals": "^1.4.0", 121 | "rollup-plugin-size-snapshot": "^0.12.0", 122 | "rollup-plugin-terser": "^6.0.1", 123 | "semver": "^6.3.0", 124 | "typescript": "3.9.7", 125 | "which": "^1.3.0", 126 | "yargs-parser": "13.1.2" 127 | }, 128 | "eslintConfig": { 129 | "extends": [ 130 | "kentcdodds", 131 | "kentcdodds/jest" 132 | ], 133 | "rules": { 134 | "no-process-exit": "off", 135 | "import/no-dynamic-require": "off", 136 | "import/no-unassigned-import": "off", 137 | "no-console": "off", 138 | "no-nested-ternary": "off" 139 | } 140 | }, 141 | "eslintIgnore": [ 142 | "node_modules", 143 | "coverage", 144 | "dist" 145 | ], 146 | "devDependencies": { 147 | "axios": "0.19.2", 148 | "handlebars": "4.7.6", 149 | "jest-in-case": "^1.0.2", 150 | "js-yaml": "3.14.0", 151 | "slash": "^3.0.0" 152 | }, 153 | "publishConfig": { 154 | "access": "public" 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /prettier.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./src/config/prettierrc'); 2 | -------------------------------------------------------------------------------- /src/cli.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const spawn = require('cross-spawn'); 3 | const program = require('commander'); 4 | const pkg = require('../package.json'); 5 | 6 | /* eslint-disable no-unused-vars */ 7 | const [executor, ignoredBin, script, ...args] = process.argv; 8 | /* eslint-enable no-unused-vars */ 9 | 10 | program.usage(` 11 | 12 | 📦 Zero (v${pkg.version}) 13 | 14 | zero 15 | 16 | Example: 17 | zero build`); 18 | 19 | program.version(pkg.version); 20 | 21 | program 22 | .command('build') 23 | .description('Builds project with Babel, Rollup, or TypeScript') 24 | .option('--bundle', 'Bundle with Rollup') 25 | .option('--rollup') 26 | .option('--roll') 27 | .option('--typescript', 'Build with TypeScript') 28 | .option('--tsc') 29 | .option('--ts') 30 | .option('--no-clean', 'Skip cleaning the dist directory') 31 | .allowUnknownOption() 32 | .action(() => { 33 | spawnScript('build'); 34 | }); 35 | 36 | program 37 | .command('bundle') 38 | .description('Bundles project into single files with Rollup') 39 | .option('--no-clean', 'Skip cleaning the dist directory') 40 | .allowUnknownOption() 41 | .action(() => { 42 | spawnScript('bundle'); 43 | }); 44 | 45 | program 46 | .command('contributors') 47 | .description('Generates markdown file with all contributors') 48 | .action(() => { 49 | spawnScript('contributors'); 50 | }); 51 | 52 | program 53 | .command('format') 54 | .description('Formats files with Prettier') 55 | .option('--no-write', 'Does not write changes to files') 56 | .allowUnknownOption() 57 | .action(() => { 58 | spawnScript('format'); 59 | }); 60 | 61 | program 62 | .command('lint') 63 | .description('Lints files with ESLint') 64 | .option('--no-cache', 'Do not use cache for linting') 65 | .allowUnknownOption() 66 | .action(() => { 67 | spawnScript('lint'); 68 | }); 69 | 70 | program 71 | .command('new') 72 | .description('Generate a new module') 73 | .allowUnknownOption() 74 | .action(() => { 75 | spawnScript('new'); 76 | }); 77 | 78 | program 79 | .command('pre-commit') 80 | .description('Lints files before staging for commit') 81 | .allowUnknownOption() 82 | .action(() => { 83 | spawnScript('pre-commit'); 84 | }); 85 | 86 | program 87 | .command('prestart') 88 | .description('Automatically install dependencies before starting') 89 | .allowUnknownOption() 90 | .action(() => { 91 | spawnScript('prestart'); 92 | }); 93 | 94 | program 95 | .command('release') 96 | .description('Publish to npm') 97 | .allowUnknownOption() 98 | .action(() => { 99 | spawnScript('release'); 100 | }); 101 | 102 | program 103 | .command('setup') 104 | .description('Sets up tooling in project') 105 | .option('babel', 'Adds a .babelrc file') 106 | .option('eslint', 'Adds a .eslintrc file') 107 | .action(cmd => { 108 | if (typeof cmd !== 'string') { 109 | cmd.outputHelp(); 110 | return; 111 | } 112 | logScriptMessage(); 113 | console.log(`Setting up ${cmd}...`); 114 | console.log(''); 115 | 116 | require(require.resolve(`./scripts/setup/${cmd}`)); 117 | }); 118 | 119 | program 120 | .command('test') 121 | .description('Run test with Jest') 122 | .option('--runInBand', 'Runs tests sequentially. Improves console.logs') 123 | .option('--no-watch', 'Does not watch for changes') 124 | .allowUnknownOption() 125 | .action(() => { 126 | spawnScript('test'); 127 | }); 128 | 129 | program 130 | .command('typecheck') 131 | .description('Check types with TypeScript') 132 | .allowUnknownOption() 133 | .action(() => { 134 | spawnScript('typecheck'); 135 | }); 136 | 137 | program 138 | .command('validate') 139 | .description('Validates project with lint, tests, and build') 140 | .allowUnknownOption() 141 | .action(() => { 142 | spawnScript('validate'); 143 | }); 144 | 145 | function getEnv() { 146 | // this is required to address an issue in cross-spawn 147 | // https://github.com/kentcdodds/kcd-scripts/issues/4 148 | return Object.keys(process.env) 149 | .filter(key => process.env[key] !== undefined) 150 | .reduce( 151 | (envCopy, key) => { 152 | envCopy[key] = process.env[key]; 153 | return envCopy; 154 | }, 155 | { 156 | [`SCRIPTS_${script.toUpperCase()}`]: true, 157 | }, 158 | ); 159 | } 160 | 161 | function logScriptMessage() { 162 | console.log(''); 163 | console.log('📦', '', `Zero ${script}...`); 164 | console.log(''); 165 | } 166 | 167 | function spawnScript(script) { 168 | const relativeScriptPath = path.join(__dirname, './scripts', script); 169 | const scriptPath = attemptResolve(relativeScriptPath); 170 | 171 | if (!scriptPath) { 172 | program.outputHelp(); 173 | process.exit(0); 174 | } 175 | 176 | logScriptMessage(); 177 | 178 | const result = spawn.sync(executor, [scriptPath, ...args], { 179 | stdio: 'inherit', 180 | env: getEnv(), 181 | }); 182 | 183 | if (result.signal) { 184 | handleSignal(result); 185 | } else { 186 | process.exit(result.status); 187 | } 188 | } 189 | 190 | function handleSignal(result) { 191 | if (result.signal === 'SIGKILL') { 192 | console.log( 193 | `The script "${script}" failed because the process exited too early. ` + 194 | 'This probably means the system ran out of memory or someone called ' + 195 | '`kill -9` on the process.', 196 | ); 197 | } else if (result.signal === 'SIGTERM') { 198 | console.log( 199 | `The script "${script}" failed because the process exited too early. ` + 200 | 'Someone might have called `kill` or `killall`, or the system could ' + 201 | 'be shutting down.', 202 | ); 203 | } 204 | process.exit(1); 205 | } 206 | 207 | function attemptResolve(...resolveArgs) { 208 | try { 209 | return require.resolve(...resolveArgs); 210 | } catch (error) { 211 | return null; 212 | } 213 | } 214 | 215 | program.parse(process.argv); 216 | 217 | if (!process.argv.slice(2).length) { 218 | program.outputHelp(); 219 | } 220 | -------------------------------------------------------------------------------- /src/config/babel-transform.js: -------------------------------------------------------------------------------- 1 | const babelJest = require('babel-jest'); 2 | 3 | // From create-react-app + kcd-scripts 4 | module.exports = babelJest.createTransformer({ 5 | presets: [require.resolve('./babelrc')], 6 | babelrc: false, 7 | configFile: false, 8 | }); 9 | -------------------------------------------------------------------------------- /src/config/babelrc.js: -------------------------------------------------------------------------------- 1 | const browserslist = require('browserslist'); 2 | const semver = require('semver'); 3 | 4 | const { ifAnyDep, parseEnv, appDirectory, pkg } = require('../utils'); 5 | 6 | const { BABEL_ENV, NODE_ENV, BUILD_FORMAT } = process.env; 7 | const isProduction = (BABEL_ENV || NODE_ENV) === 'production'; 8 | const isTest = (BABEL_ENV || NODE_ENV) === 'test'; 9 | const isPreact = parseEnv('BUILD_PREACT', false); 10 | const isRollup = parseEnv('BUILD_ROLLUP', false); 11 | const isUMD = BUILD_FORMAT === 'umd'; 12 | const isCJS = BUILD_FORMAT === 'cjs'; 13 | const isEs = BUILD_FORMAT === 'es'; 14 | const isWebpack = parseEnv('BUILD_WEBPACK', false); 15 | const treeshake = parseEnv('BUILD_TREESHAKE', isRollup || isWebpack) || isEs; 16 | const alias = parseEnv('BUILD_ALIAS', isPreact ? { react: 'preact' } : null); 17 | 18 | /** 19 | * Custom conditionals 20 | */ 21 | const isTargetNode = process.env.BABEL_TARGET === 'node'; 22 | // Prefer to target browsers 23 | const isTargetBrowser = !isTargetNode; 24 | // Prefer without runtime 25 | const isBabelRuntime = parseEnv('BUILD_RUNTIME', false); 26 | 27 | /** 28 | * use the strategy declared by browserslist to load browsers configuration. 29 | * fallback to the default if don't found custom configuration 30 | * @see https://github.com/browserslist/browserslist/blob/master/node.js#L139 31 | */ 32 | const browsersConfig = browserslist.loadConfig({ path: appDirectory }) || [ 33 | 'ie 10', 34 | 'ios 7', 35 | ]; 36 | 37 | const envTargets = isTest 38 | ? { node: 'current' } 39 | : isWebpack || isRollup || isTargetBrowser || isEs 40 | ? { browsers: browsersConfig } 41 | : { node: getNodeVersion(pkg) }; 42 | 43 | const envOptions = { modules: false, loose: true, targets: envTargets }; 44 | 45 | // The follow jestConfig is a combination of kcd-scripts and create-react-app. 46 | // kcd-script's setup was used as the foundation because of it's simplicity. 47 | // CRA's settings have been added for TypeScript support. 48 | module.exports = () => ({ 49 | presets: [ 50 | // From kcd-scripts 51 | [require.resolve('@babel/preset-env'), envOptions], 52 | // From kcd-scripts 53 | ifAnyDep( 54 | ['react', 'preact'], 55 | [ 56 | require.resolve('@babel/preset-react'), 57 | { 58 | pragma: isPreact ? 'React.h' : undefined, 59 | development: isTest, 60 | // From create-react-app 61 | // Will use the native built-in instead of trying to polyfill 62 | // behavior for any plugins that require one. 63 | useBuiltIns: true, 64 | }, 65 | ], 66 | ), 67 | // From create-react-app 68 | // Strip flow types before any other transform, emulating the behavior 69 | // order as-if the browser supported all of the succeeding features 70 | // https://github.com/facebook/create-react-app/pull/5182 71 | // We will conditionally enable this plugin below in overrides as it clashes with 72 | // @babel/plugin-proposal-decorators when using TypeScript. 73 | // https://github.com/facebook/create-react-app/issues/5741 74 | [require.resolve('@babel/preset-flow')], 75 | // From create-react-app 76 | [require.resolve('@babel/preset-typescript')], 77 | ].filter(Boolean), 78 | plugins: [ 79 | // From create-react-app 80 | require.resolve('@babel/plugin-transform-flow-strip-types'), 81 | // From create-react-app 82 | // Experimental macros support. Will be documented after it's had some time 83 | // in the wild. 84 | require.resolve('babel-plugin-macros'), 85 | // From create-react-app 86 | // Necessary to include regardless of the environment because 87 | // in practice some other transforms (such as object-rest-spread) 88 | // don't work without it: https://github.com/babel/babel/issues/7215 89 | require.resolve('@babel/plugin-transform-destructuring'), 90 | // From create-react-app 91 | // Turn on legacy decorators for TypeScript files 92 | [require.resolve('@babel/plugin-proposal-decorators'), false], 93 | // From create-react-app 94 | // class { handleClick = () => { } } 95 | // Enable loose mode to use assignment instead of defineProperty 96 | // See discussion in https://github.com/facebook/create-react-app/issues/4263 97 | [ 98 | require.resolve('@babel/plugin-proposal-class-properties'), 99 | { loose: true }, 100 | ], 101 | // From create-react-app 102 | // The following two plugins use Object.assign directly, instead of Babel's 103 | // extends helper. Note that this assumes `Object.assign` is available. 104 | // { ...todo, completed: true } 105 | [ 106 | require('@babel/plugin-proposal-object-rest-spread').default, 107 | { 108 | useBuiltIns: true, 109 | }, 110 | ], 111 | // From create-react-app 112 | // Polyfills the runtime needed for async/await, generators, and friends 113 | // https://babeljs.io/docs/en/babel-plugin-transform-runtime 114 | isBabelRuntime 115 | ? [ 116 | require.resolve('@babel/plugin-transform-runtime'), 117 | { 118 | useESModules: treeshake && !isCJS, 119 | corejs: false, 120 | regenerator: true, 121 | }, 122 | ] 123 | : null, 124 | // From create-react-app 125 | // Remove PropTypes from production build 126 | isProduction 127 | ? [ 128 | require.resolve( 129 | 'babel-plugin-transform-react-remove-prop-types', 130 | ), 131 | isPreact ? { removeImport: true } : { mode: 'unsafe-wrap' }, 132 | ] 133 | : null, 134 | // From create-react-app 135 | // Adds syntax support for import() 136 | require.resolve('@babel/plugin-syntax-dynamic-import'), 137 | // From kcd-scripts 138 | alias 139 | ? [ 140 | require.resolve('babel-plugin-module-resolver'), 141 | { root: ['./src'], alias }, 142 | ] 143 | : null, 144 | // From kcd-scripts 145 | isUMD 146 | ? require.resolve( 147 | 'babel-plugin-transform-inline-environment-variables', 148 | ) 149 | : null, 150 | // From kcd-scripts 151 | require.resolve('babel-plugin-minify-dead-code-elimination'), 152 | // From kcd-scripts 153 | treeshake 154 | ? null 155 | : require.resolve('@babel/plugin-transform-modules-commonjs'), 156 | require.resolve('babel-plugin-inline-svg'), 157 | require.resolve('babel-plugin-emotion'), 158 | // Custom 159 | require.resolve('@babel/plugin-proposal-optional-chaining'), 160 | ].filter(Boolean), 161 | // From create-react-app 162 | overrides: [ 163 | { 164 | test: /\.{js,jsx,ts,tsx}?$/, 165 | plugins: [ 166 | require.resolve('@babel/plugin-transform-flow-strip-types'), 167 | ], 168 | }, 169 | { 170 | test: /\.{js,jsx,ts,tsx}?$/, 171 | plugins: [ 172 | [ 173 | require.resolve('@babel/plugin-proposal-decorators'), 174 | { legacy: true }, 175 | ], 176 | ], 177 | }, 178 | ].filter(Boolean), 179 | }); 180 | 181 | function getNodeVersion({ engines: { node: nodeVersion = '8' } = {} }) { 182 | const oldestVersion = semver 183 | .validRange(nodeVersion) 184 | .replace(/[>=<|]/g, ' ') 185 | .split(' ') 186 | .filter(Boolean) 187 | .sort(semver.compare)[0]; 188 | if (!oldestVersion) { 189 | throw new Error( 190 | `Unable to determine the oldest version in the range in your package.json at engines.node: "${nodeVersion}". Please attempt to make it less ambiguous.`, 191 | ); 192 | } 193 | return oldestVersion; 194 | } 195 | -------------------------------------------------------------------------------- /src/config/eslintignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | coverage/ 3 | dist/ 4 | build/ 5 | out/ 6 | .next/ 7 | -------------------------------------------------------------------------------- /src/config/eslintrc.js: -------------------------------------------------------------------------------- 1 | const { ifAnyDep } = require('../utils'); 2 | 3 | module.exports = { 4 | extends: [ 5 | 'eslint:recommended', 6 | 'eslint-config-kentcdodds/jest', 7 | ifAnyDep('react', require.resolve('eslint-config-react-app')), 8 | 'prettier', 9 | ].filter(Boolean), 10 | parser: 'babel-eslint', 11 | parserOptions: { 12 | ecmaFeatures: { 13 | jsx: true, 14 | modules: true, 15 | }, 16 | }, 17 | plugins: [ 18 | 'better-styled-components', 19 | 'react', 20 | 'simple-import-sort', 21 | 'sort-keys-fix', 22 | 'sort-destructure-keys', 23 | ], 24 | env: { 25 | browser: true, 26 | node: true, 27 | jasmine: true, 28 | }, 29 | globals: { 30 | console: true, 31 | global: true, 32 | module: true, 33 | }, 34 | rules: { 35 | 'better-styled-components/sort-declarations-alphabetically': 2, 36 | 'sort-destructure-keys/sort-destructure-keys': 2, 37 | 'sort-keys-fix/sort-keys-fix': 'warn', 38 | 'sort-imports': 'off', 39 | 'import/order': 'off', 40 | 'simple-import-sort/sort': 'error', 41 | 'react/jsx-sort-props': 'error', 42 | }, 43 | }; 44 | -------------------------------------------------------------------------------- /src/config/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | babel: require('./babelrc'), 3 | eslint: require('./eslintrc'), 4 | jest: require('./jest.config'), 5 | lintStaged: require('./lintstagedrc'), 6 | prettier: require('./prettierrc'), 7 | getRollupConfig: () => require('./rollup.config'), 8 | }; 9 | -------------------------------------------------------------------------------- /src/config/jest.config.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | const { ifAnyDep, hasFile, hasPkgProp, fromRoot, there } = require('../utils'); 4 | 5 | const here = (p) => path.join(__dirname, p); 6 | 7 | const useBuiltInBabelConfig = !hasFile('.babelrc') && !hasPkgProp('babel'); 8 | // Disabling 100% test coverage defaults. 9 | const shouldHaveTotalCoverage = false; 10 | 11 | const hasSrcSetupFile = fs.existsSync(there('./src/setupTests.js')); 12 | const hasScriptSetupFile = fs.existsSync(there('./scripts/setupTests.js')); 13 | 14 | const ignores = [ 15 | '__mocks__', 16 | '__stories__', 17 | '/__fixtures__/', 18 | '/__tests__/helpers/', 19 | '/__tests__/utils/', 20 | '/fixtures/', 21 | '/node_modules/', 22 | ]; 23 | 24 | // The follow jestConfig is a combination of kcd-scripts and create-react-app. 25 | // kcd-script's setup was used as the foundation because of it's simplicity. 26 | // CRA's settings have been added for TypeScript support. 27 | const jestConfig = { 28 | roots: [fromRoot('src')], 29 | testEnvironment: ifAnyDep(['webpack', 'rollup', 'react'], 'jsdom', 'node'), 30 | testURL: 'http://localhost', 31 | moduleFileExtensions: ['js', 'jsx', 'json', 'ts', 'tsx'], 32 | // From create-react-app 33 | collectCoverageFrom: [ 34 | 'src/**/*.+(js|jsx|ts|tsx)', 35 | '!src/**/*.d.ts', 36 | 'packages/**/*.+(js|jsx|ts|tsx)', 37 | '!packages/**/*.d.ts', 38 | ], 39 | // From create-react-app 40 | setupFiles: [require.resolve('react-app-polyfill/jsdom')], 41 | testMatch: [ 42 | '**/__tests__/**/*.+(js|jsx|ts|tsx)', 43 | '/src/**/?(*.)(spec|test).{js,jsx,ts,tsx}', 44 | '/packages/**/?(*.)(spec|test).{js,jsx,ts,tsx}', 45 | ], 46 | testPathIgnorePatterns: [...ignores], 47 | coveragePathIgnorePatterns: [...ignores, 'src/(umd|cjs|esm)-entry.js$'], 48 | transformIgnorePatterns: [ 49 | '[/\\\\]node_modules[/\\\\].+\\.(js|jsx|ts|tsx)$', 50 | ], 51 | watchPlugins: [ 52 | require.resolve('jest-watch-typeahead/filename'), 53 | require.resolve('jest-watch-typeahead/testname'), 54 | ], 55 | }; 56 | 57 | if (useBuiltInBabelConfig) { 58 | jestConfig.transform = { 59 | '^.+\\.(js|jsx|ts|tsx)$': here('./babel-transform'), 60 | }; 61 | } 62 | 63 | // Disabled by default. 64 | if (shouldHaveTotalCoverage) { 65 | jestConfig.coverageThreshold = { 66 | global: { 67 | branches: 100, 68 | functions: 100, 69 | lines: 100, 70 | statements: 100, 71 | }, 72 | }; 73 | } 74 | 75 | if (hasSrcSetupFile) { 76 | jestConfig.setupFilesAfterEnv = ['/src/setupTests.js']; 77 | } 78 | if (hasScriptSetupFile) { 79 | jestConfig.setupFilesAfterEnv = ['/scripts/setupTests.js']; 80 | } 81 | 82 | module.exports = jestConfig; 83 | -------------------------------------------------------------------------------- /src/config/lintstagedrc.js: -------------------------------------------------------------------------------- 1 | const { resolveZeroScripts, resolveBin, isOptedOut } = require('../utils'); 2 | 3 | const zeroScripts = resolveZeroScripts(); 4 | const doctoc = resolveBin('doctoc'); 5 | 6 | module.exports = { 7 | 'README.md': [`${doctoc} --maxlevel 3 --notitle`, 'git add'], 8 | '*.+(js|jsx|json|yml|yaml|css|less|scss|ts|tsx|md|graphql|mdx|vue)': [ 9 | isOptedOut('autoformat', null, `${zeroScripts} format`), 10 | `${zeroScripts} lint`, 11 | `${zeroScripts} test --findRelatedTests`, 12 | isOptedOut('autoformat', null, 'git add'), 13 | ].filter(Boolean), 14 | }; 15 | -------------------------------------------------------------------------------- /src/config/prettierignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | coverage/ 3 | dist/ 4 | build/ 5 | out/ 6 | .next/ 7 | -------------------------------------------------------------------------------- /src/config/prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | printWidth: 80, 3 | tabWidth: 4, 4 | useTabs: true, 5 | semi: true, 6 | singleQuote: true, 7 | trailingComma: 'all', 8 | bracketSpacing: true, 9 | jsxBracketSameLine: false, 10 | }; 11 | -------------------------------------------------------------------------------- /src/config/rollup.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const { babel: rollupBabel } = require('@rollup/plugin-babel'); 3 | const commonjs = require('@rollup/plugin-commonjs'); 4 | const json = require('@rollup/plugin-json'); 5 | const { 6 | DEFAULTS: nodeResolveDefaults, 7 | nodeResolve, 8 | } = require('@rollup/plugin-node-resolve'); 9 | const replace = require('@rollup/plugin-replace'); 10 | const glob = require('glob'); 11 | const camelcase = require('lodash.camelcase'); 12 | const { terser } = require('rollup-plugin-terser'); 13 | const nodeBuiltIns = require('rollup-plugin-node-builtins'); 14 | const nodeGlobals = require('rollup-plugin-node-globals'); 15 | const { sizeSnapshot } = require('rollup-plugin-size-snapshot'); 16 | const omit = require('lodash.omit'); 17 | const { 18 | pkg, 19 | hasFile, 20 | hasPkgProp, 21 | hasTypescript, 22 | parseEnv, 23 | fromRoot, 24 | uniq, 25 | writeExtraEntry, 26 | } = require('../utils'); 27 | 28 | const here = (p) => path.join(__dirname, p); 29 | const capitalize = (s) => s[0].toUpperCase() + s.slice(1); 30 | 31 | const minify = parseEnv('BUILD_MINIFY', false); 32 | const format = process.env.BUILD_FORMAT; 33 | const isPreact = parseEnv('BUILD_PREACT', false); 34 | const isNode = parseEnv('BUILD_NODE', false); 35 | const name = process.env.BUILD_NAME || capitalize(camelcase(pkg.name)); 36 | const useSizeSnapshot = parseEnv('BUILD_SIZE_SNAPSHOT', false); 37 | 38 | const esm = format === 'esm'; 39 | const umd = format === 'umd'; 40 | 41 | const defaultGlobals = Object.keys(pkg.peerDependencies || {}).reduce( 42 | (deps, dep) => { 43 | deps[dep] = capitalize(camelcase(dep)); 44 | return deps; 45 | }, 46 | {}, 47 | ); 48 | 49 | const deps = Object.keys(pkg.dependencies || {}); 50 | const peerDeps = Object.keys(pkg.peerDependencies || {}); 51 | const defaultExternal = umd ? peerDeps : deps.concat(peerDeps); 52 | 53 | const input = glob.sync( 54 | fromRoot( 55 | process.env.BUILD_INPUT || 56 | (hasTypescript ? 'src/index.{js,ts,tsx}' : 'src/index.js'), 57 | ), 58 | ); 59 | const codeSplitting = input.length > 1; 60 | 61 | if ( 62 | codeSplitting && 63 | uniq(input.map((single) => path.basename(single))).length !== input.length 64 | ) { 65 | throw new Error( 66 | 'Filenames of code-splitted entries should be unique to get deterministic output filenames.' + 67 | `\nReceived those: ${input}.`, 68 | ); 69 | } 70 | 71 | const filenameSuffix = process.env.BUILD_FILENAME_SUFFIX || ''; 72 | const filenamePrefix = 73 | process.env.BUILD_FILENAME_PREFIX || (isPreact ? 'preact/' : ''); 74 | const globals = parseEnv( 75 | 'BUILD_GLOBALS', 76 | isPreact 77 | ? Object.assign(defaultGlobals, { preact: 'preact' }) 78 | : defaultGlobals, 79 | ); 80 | const external = parseEnv( 81 | 'BUILD_EXTERNAL', 82 | isPreact 83 | ? defaultExternal.concat(['preact', 'prop-types']) 84 | : defaultExternal, 85 | ).filter((e, i, arry) => arry.indexOf(e) === i); 86 | 87 | if (isPreact) { 88 | delete globals.react; 89 | delete globals['prop-types']; // TODO: is this necessary? 90 | external.splice(external.indexOf('react'), 1); 91 | } 92 | 93 | const externalPattern = new RegExp(`^(${external.join('|')})($|/)`); 94 | 95 | function externalPredicate(id) { 96 | const isDep = external.length > 0 && externalPattern.test(id); 97 | if (umd) { 98 | // for UMD, we want to bundle all non-peer deps 99 | return isDep; 100 | } 101 | // for esm/cjs we want to make all node_modules external 102 | // TODO: support bundledDependencies if someone needs it ever... 103 | const isNodeModule = id.includes('node_modules'); 104 | const isRelative = id.startsWith('.'); 105 | return isDep || (!isRelative && !path.isAbsolute(id)) || isNodeModule; 106 | } 107 | 108 | const filename = [ 109 | pkg.name, 110 | filenameSuffix, 111 | `.${format}`, 112 | minify ? '.min' : null, 113 | '.js', 114 | ] 115 | .filter(Boolean) 116 | .join(''); 117 | 118 | const dirpath = path.join(...[filenamePrefix, 'dist'].filter(Boolean)); 119 | 120 | const output = [ 121 | { 122 | name, 123 | ...(codeSplitting 124 | ? { dir: path.join(dirpath, format) } 125 | : { file: path.join(dirpath, filename) }), 126 | format: esm ? 'es' : format, 127 | exports: esm ? 'named' : 'auto', 128 | globals, 129 | }, 130 | ]; 131 | 132 | const useBuiltinConfig = 133 | !hasFile('.babelrc') && 134 | !hasFile('.babelrc.js') && 135 | !hasFile('babel.config.js') && 136 | !hasPkgProp('babel'); 137 | const babelPresets = useBuiltinConfig ? [here('../config/babelrc.js')] : []; 138 | 139 | const replacements = Object.entries( 140 | umd ? process.env : omit(process.env, ['NODE_ENV']), 141 | ).reduce((acc, [key, value]) => { 142 | let val; 143 | if (value === 'true' || value === 'false' || Number.isInteger(+value)) { 144 | val = value; 145 | } else { 146 | val = JSON.stringify(value); 147 | } 148 | acc[`process.env.${key}`] = val; 149 | return acc; 150 | }, {}); 151 | 152 | const extensions = hasTypescript 153 | ? [...nodeResolveDefaults.extensions, '.ts', '.tsx'] 154 | : nodeResolveDefaults.extensions; 155 | 156 | module.exports = { 157 | input: codeSplitting ? input : input[0], 158 | output, 159 | external: externalPredicate, 160 | plugins: [ 161 | isNode ? nodeGlobals() : null, 162 | nodeResolve({ 163 | preferBuiltins: isNode, 164 | mainFields: ['module', 'main', 'jsnext', 'browser'], 165 | }), 166 | commonjs({ include: 'node_modules/**' }), 167 | json(), 168 | rollupBabel({ 169 | presets: babelPresets, 170 | babelrc: !useBuiltinConfig, 171 | babelHelpers: 'bundled', 172 | extensions, 173 | }), 174 | replace(replacements), 175 | useSizeSnapshot ? sizeSnapshot({ printInfo: false }) : null, 176 | minify ? terser() : null, 177 | codeSplitting && 178 | ((writes = 0) => ({ 179 | onwrite() { 180 | if (++writes !== input.length) { 181 | return; 182 | } 183 | 184 | input 185 | .filter((single) => single.indexOf('index.js') === -1) 186 | .forEach((single) => { 187 | const chunk = path.basename(single); 188 | 189 | writeExtraEntry(chunk.replace(/\..+$/, ''), { 190 | cjs: `${dirpath}/cjs/${chunk}`, 191 | esm: `${dirpath}/esm/${chunk}`, 192 | }); 193 | }); 194 | }, 195 | }))(), 196 | ].filter(Boolean), 197 | }; 198 | -------------------------------------------------------------------------------- /src/exec/typescript.js: -------------------------------------------------------------------------------- 1 | const spawn = require('cross-spawn'); 2 | const { resolveBin } = require('../utils'); 3 | 4 | exports.execTypeScript = async (args = []) => { 5 | try { 6 | const result = spawn.sync( 7 | resolveBin('typescript', { executable: 'tsc' }), 8 | args, 9 | { stdio: 'inherit' }, 10 | ); 11 | 12 | if (result.status) { 13 | return Promise.reject(1); 14 | } else { 15 | return Promise.resolve(0); 16 | } 17 | } catch (err) { 18 | console.log(err); 19 | return Promise.reject(1); 20 | } 21 | }; 22 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | let shouldThrow; 3 | try { 4 | shouldThrow = 5 | require(`${process.cwd()}/package.json`).name === '@itsjonq/zero' && 6 | Number(process.version.slice(1).split('.')[0]) < 8; 7 | } catch (error) { 8 | // ignore 9 | } 10 | 11 | if (shouldThrow) { 12 | throw new Error( 13 | 'You must use Node version 8 or greater to run the scripts within @itsjonq/zero ' + 14 | 'because we dogfood the untranspiled version of the scripts.', 15 | ); 16 | } 17 | 18 | require('./cli'); 19 | -------------------------------------------------------------------------------- /src/scripts/build/babel.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const spawn = require('cross-spawn'); 3 | const { hasPkgProp, resolveBin, hasFile } = require('../../utils'); 4 | 5 | const filteredArgs = ['--no-clean']; 6 | const args = process.argv.slice(2).filter(a => !filteredArgs.includes(a)); 7 | const here = p => path.join(__dirname, p); 8 | 9 | exports.buildBabel = async () => { 10 | try { 11 | const useBuiltinConfig = 12 | !args.includes('--presets') && 13 | !hasFile('.babelrc') && 14 | !hasFile('.babelrc.js') && 15 | !hasFile('babel.config.js') && 16 | !hasPkgProp('babel'); 17 | 18 | const config = useBuiltinConfig 19 | ? ['--presets', here('../../config/babelrc.js')] 20 | : []; 21 | 22 | const ignore = args.includes('--include') 23 | ? [] 24 | : ['--ignore', '__tests__,__mocks__']; 25 | 26 | const copyFiles = args.includes('--no-copy-files') 27 | ? [] 28 | : ['--copy-files']; 29 | 30 | const useSpecifiedOutDir = args.includes('--out-dir'); 31 | const outDir = useSpecifiedOutDir ? [] : ['--out-dir', 'dist']; 32 | 33 | // Add TypeScript support! 34 | const extensions = args.includes('--extensions') 35 | ? [] 36 | : ['--extensions', '.js,.jsx,.ts,.tsx']; 37 | 38 | const result = spawn.sync( 39 | resolveBin('@babel/cli', { executable: 'babel' }), 40 | [ 41 | ...outDir, 42 | ...copyFiles, 43 | ...ignore, 44 | ...config, 45 | ...extensions, 46 | 'src', 47 | ].concat(args), 48 | { stdio: 'inherit' }, 49 | ); 50 | 51 | if (result.status) { 52 | return Promise.reject(1); 53 | } else { 54 | return Promise.resolve(0); 55 | } 56 | } catch (err) { 57 | console.log(err); 58 | return Promise.reject(1); 59 | } 60 | }; 61 | -------------------------------------------------------------------------------- /src/scripts/build/clean.js: -------------------------------------------------------------------------------- 1 | const rimraf = require('rimraf'); 2 | const { fromRoot } = require('../../utils'); 3 | 4 | const args = process.argv.slice(2); 5 | const useSpecifiedOutDir = args.includes('--out-dir'); 6 | 7 | exports.clean = () => { 8 | if (!useSpecifiedOutDir && !args.includes('--no-clean')) { 9 | console.log(`Cleaning ${fromRoot('dist')}...`); 10 | console.log(); 11 | rimraf.sync(fromRoot('dist')); 12 | } 13 | }; 14 | -------------------------------------------------------------------------------- /src/scripts/build/index.js: -------------------------------------------------------------------------------- 1 | if (process.argv.includes('--browser')) { 2 | console.error('--browser has been deprecated, use --bundle instead'); 3 | } 4 | 5 | const shouldBundle = 6 | process.argv.includes('--bundle') || 7 | process.argv.includes('--browser') || 8 | process.argv.includes('--rollup') || 9 | process.argv.includes('--roll'); 10 | 11 | const shouldCompileWithTypeScript = 12 | process.argv.includes('--typescript') || 13 | process.argv.includes('--tsc') || 14 | process.argv.includes('--ts'); 15 | 16 | const bundle = () => { 17 | console.log('Compiling with Rollup...'); 18 | require('./rollup'); 19 | }; 20 | 21 | const build = async () => { 22 | const { clean } = require('./clean'); 23 | 24 | clean(); 25 | 26 | if (shouldCompileWithTypeScript) { 27 | require('../compile/typescript').compileTypeScript(); 28 | } else { 29 | require('../compile/babel').compileBabel(); 30 | } 31 | }; 32 | 33 | if (shouldBundle) { 34 | bundle(); 35 | } else { 36 | build(); 37 | } 38 | -------------------------------------------------------------------------------- /src/scripts/build/rollup.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const spawn = require('cross-spawn'); 3 | const glob = require('fast-glob'); 4 | const rimraf = require('rimraf'); 5 | const yargsParser = require('yargs-parser'); 6 | const { 7 | hasFile, 8 | resolveBin, 9 | fromRoot, 10 | getConcurrentlyArgs, 11 | writeExtraEntry, 12 | } = require('../../utils'); 13 | 14 | const crossEnv = resolveBin('cross-env'); 15 | const rollup = resolveBin('rollup'); 16 | const args = process.argv.slice(2); 17 | const here = p => path.join(__dirname, p); 18 | const hereRelative = p => here(p).replace(process.cwd(), '.'); 19 | const parsedArgs = yargsParser(args); 20 | 21 | const useBuiltinConfig = 22 | !args.includes('--config') && !hasFile('rollup.config.js'); 23 | const config = useBuiltinConfig 24 | ? `--config ${hereRelative('../../config/rollup.config.js')}` 25 | : args.includes('--config') 26 | ? '' 27 | : '--config'; // --config will pick up the rollup.config.js file 28 | 29 | const environment = parsedArgs.environment 30 | ? `--environment ${parsedArgs.environment}` 31 | : ''; 32 | const watch = parsedArgs.watch ? '--watch' : ''; 33 | const sizeSnapshot = parsedArgs['size-snapshot']; 34 | 35 | let formats = ['esm', 'cjs', 'umd', 'umd.min']; 36 | 37 | if (typeof parsedArgs.bundle === 'string') { 38 | formats = parsedArgs.bundle.split(','); 39 | } 40 | 41 | const defaultEnv = 'BUILD_ROLLUP=true'; 42 | 43 | const getCommand = (env, ...flags) => 44 | [crossEnv, defaultEnv, env, rollup, config, environment, watch, ...flags] 45 | .filter(Boolean) 46 | .join(' '); 47 | 48 | const buildPreact = args.includes('--p-react'); 49 | const scripts = buildPreact 50 | ? getPReactScripts() 51 | : getConcurrentlyArgs(getCommands()); 52 | 53 | const cleanBuildDirs = !args.includes('--no-clean'); 54 | 55 | if (cleanBuildDirs) { 56 | rimraf.sync(fromRoot('dist')); 57 | 58 | if (buildPreact) { 59 | rimraf.sync(fromRoot('preact')); 60 | } 61 | } 62 | 63 | const result = spawn.sync(resolveBin('concurrently'), scripts, { 64 | stdio: 'inherit', 65 | }); 66 | 67 | if (result.status === 0 && buildPreact && !args.includes('--no-package-json')) { 68 | writeExtraEntry( 69 | 'preact', 70 | { 71 | cjs: glob.sync(fromRoot('preact/**/*.cjs.js'))[0], 72 | esm: glob.sync(fromRoot('preact/**/*.esm.js'))[0], 73 | }, 74 | false, 75 | ); 76 | } 77 | 78 | function getPReactScripts() { 79 | const reactCommands = prefixKeys('react.', getCommands()); 80 | const preactCommands = prefixKeys('preact.', getCommands({ preact: true })); 81 | return getConcurrentlyArgs(Object.assign(reactCommands, preactCommands)); 82 | } 83 | 84 | function prefixKeys(prefix, object) { 85 | return Object.entries(object).reduce((cmds, [key, value]) => { 86 | cmds[`${prefix}${key}`] = value; 87 | return cmds; 88 | }, {}); 89 | } 90 | 91 | function getCommands({ preact = false } = {}) { 92 | return formats.reduce((cmds, format) => { 93 | const [formatName, minify = false] = format.split('.'); 94 | const nodeEnv = minify ? 'production' : 'development'; 95 | const sourceMap = formatName === 'umd' ? '--sourcemap' : ''; 96 | const buildMinify = Boolean(minify); 97 | 98 | cmds[format] = getCommand( 99 | [ 100 | `BUILD_FORMAT=${formatName}`, 101 | `BUILD_MINIFY=${buildMinify}`, 102 | `NODE_ENV=${nodeEnv}`, 103 | `BUILD_PREACT=${preact}`, 104 | `BUILD_SIZE_SNAPSHOT=${sizeSnapshot}`, 105 | `BUILD_NODE=${process.env.BUILD_NODE || false}`, 106 | `BUILD_REACT_NATIVE=${process.env.BUILD_REACT_NATIVE || false}`, 107 | ].join(' '), 108 | sourceMap, 109 | ); 110 | return cmds; 111 | }, {}); 112 | } 113 | 114 | process.exit(result.status); 115 | -------------------------------------------------------------------------------- /src/scripts/bundle.js: -------------------------------------------------------------------------------- 1 | const { clean } = require('./build/clean'); 2 | 3 | clean(); 4 | 5 | require('./build/rollup'); 6 | -------------------------------------------------------------------------------- /src/scripts/compile/babel.js: -------------------------------------------------------------------------------- 1 | const args = process.argv.slice(2); 2 | 3 | exports.compileBabel = async () => { 4 | console.log('Compiling with Babel...'); 5 | 6 | try { 7 | const { buildBabel } = require('../build/babel'); 8 | const result = await buildBabel(args); 9 | process.exit(result); 10 | } catch (err) { 11 | console.log(err); 12 | console.log('Failed to compile with Babel :('); 13 | } 14 | }; 15 | -------------------------------------------------------------------------------- /src/scripts/compile/typescript.js: -------------------------------------------------------------------------------- 1 | const args = process.argv.slice(2); 2 | const buildArgs = ['--no-clean', '--typescript', '--ts', '--tsc']; 3 | 4 | exports.compileTypeScript = async () => { 5 | const { tsConfigSrc, hasTsConfig } = require('../../utils'); 6 | 7 | const tsConfig = tsConfigSrc(); 8 | 9 | if (!hasTsConfig()) { 10 | console.log(`Could not find ${tsConfig}`); 11 | process.exit(1); 12 | } 13 | 14 | const tsArgs = args.filter(a => !buildArgs.includes(a)); 15 | 16 | try { 17 | if (args.includes('--emitDeclarationOnly')) { 18 | console.log('Compiling TypeScript declarations only...'); 19 | } else { 20 | console.log('Compiling with TypeScript...'); 21 | } 22 | 23 | console.log(`Loading ${tsConfig}...`); 24 | 25 | const { execTypeScript } = require('../../exec/typescript'); 26 | const result = await execTypeScript(tsArgs); 27 | 28 | if (result === 1) { 29 | console.log('Issue compiling with TypeScript :('); 30 | } else { 31 | console.log('Successfully compiled with TypeScript!'); 32 | } 33 | 34 | process.exit(result); 35 | } catch (err) { 36 | console.log(err); 37 | console.log('Failed to compile with TypeScript :('); 38 | } 39 | }; 40 | -------------------------------------------------------------------------------- /src/scripts/contributors.js: -------------------------------------------------------------------------------- 1 | const spawn = require('cross-spawn'); 2 | const { resolveBin } = require('../utils'); 3 | 4 | const args = process.argv.slice(2); 5 | 6 | const result = spawn.sync( 7 | resolveBin('all-contributors-cli', { executable: 'all-contributors' }), 8 | args, 9 | { 10 | stdio: 'inherit', 11 | }, 12 | ); 13 | 14 | process.exit(result.status); 15 | -------------------------------------------------------------------------------- /src/scripts/format.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const spawn = require('cross-spawn'); 3 | const yargsParser = require('yargs-parser'); 4 | const { hasPkgProp, resolveBin, hasFile } = require('../utils'); 5 | 6 | const args = process.argv.slice(2); 7 | const parsedArgs = yargsParser(args); 8 | 9 | const here = p => path.join(__dirname, p); 10 | const hereRelative = p => here(p).replace(process.cwd(), '.'); 11 | 12 | const useBuiltinConfig = 13 | !args.includes('--config') && 14 | !hasFile('.prettierrc') && 15 | !hasFile('.prettierrc.js') && 16 | !hasFile('prettier.config.js') && 17 | !hasPkgProp('prettierrc'); 18 | const config = useBuiltinConfig 19 | ? ['--config', hereRelative('../config/prettierrc.js')] 20 | : []; 21 | 22 | const useBuiltinIgnore = 23 | !args.includes('--ignore-path') && !hasFile('.prettierignore'); 24 | const ignore = useBuiltinIgnore 25 | ? ['--ignore-path', hereRelative('../config/prettierignore')] 26 | : []; 27 | 28 | const write = args.includes('--no-write') ? [] : ['--write']; 29 | 30 | // this ensures that when running format as a pre-commit hook and we get 31 | // the full file path, we make that non-absolute so it is treated as a glob, 32 | // This way the prettierignore will be applied 33 | const relativeArgs = args.map(a => a.replace(`${process.cwd()}/`, '')); 34 | 35 | const filesToApply = parsedArgs._.length 36 | ? [] 37 | : ['**/*.+(js|json|less|css|ts|tsx|md)']; 38 | 39 | const result = spawn.sync( 40 | resolveBin('prettier'), 41 | [...config, ...ignore, ...write, ...filesToApply].concat(relativeArgs), 42 | { stdio: 'inherit' }, 43 | ); 44 | 45 | process.exit(result.status); 46 | -------------------------------------------------------------------------------- /src/scripts/lint.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const spawn = require('cross-spawn'); 3 | const yargsParser = require('yargs-parser'); 4 | const { hasPkgProp, resolveBin, hasFile } = require('../utils'); 5 | 6 | let args = process.argv.slice(2); 7 | const here = p => path.join(__dirname, p); 8 | const hereRelative = p => here(p).replace(process.cwd(), '.'); 9 | const parsedArgs = yargsParser(args); 10 | 11 | const useBuiltinConfig = 12 | !args.includes('--config') && 13 | !hasFile('.eslintrc') && 14 | !hasFile('.eslintrc.js') && 15 | !hasPkgProp('eslintConfig'); 16 | 17 | const config = useBuiltinConfig 18 | ? ['--config', hereRelative('../config/eslintrc.js')] 19 | : []; 20 | 21 | const useBuiltinIgnore = 22 | !args.includes('--ignore-path') && 23 | !hasFile('.eslintignore') && 24 | !hasPkgProp('eslintIgnore'); 25 | 26 | const ignore = useBuiltinIgnore 27 | ? ['--ignore-path', hereRelative('../config/eslintignore')] 28 | : []; 29 | 30 | const cache = args.includes('--no-cache') ? [] : ['--cache']; 31 | 32 | const filesGiven = parsedArgs._.length > 0; 33 | 34 | const filesToApply = filesGiven ? [] : ['.']; 35 | 36 | if (filesGiven) { 37 | // we need to take all the flag-less arguments (the files that should be linted) 38 | // and filter out the ones that aren't js files. Otherwise json or css files 39 | // may be passed through 40 | args = args.filter(a => !parsedArgs._.includes(a) || a.endsWith('.js')); 41 | } 42 | 43 | const result = spawn.sync( 44 | resolveBin('eslint'), 45 | [...config, ...ignore, ...cache, ...args, ...filesToApply], 46 | { stdio: 'inherit' }, 47 | ); 48 | 49 | process.exit(result.status); 50 | -------------------------------------------------------------------------------- /src/scripts/new.js: -------------------------------------------------------------------------------- 1 | const spawn = require('cross-spawn'); 2 | const fs = require('fs'); 3 | const path = require('path'); 4 | const inquirer = require('inquirer'); 5 | const glob = require('fast-glob'); 6 | const mkdirp = require('mkdirp'); 7 | const template = require('lodash.template'); 8 | const pkg = require('../../package.json'); 9 | 10 | const here = p => path.resolve(__dirname, p); 11 | 12 | exports.execNew = async () => { 13 | console.log('Creating a new module\n'); 14 | 15 | const pwd = process.cwd(); 16 | const prompts = await inquirer.prompt([ 17 | { 18 | type: 'input', 19 | name: 'module_name', 20 | message: "Module name? (Don't include @scope)", 21 | }, 22 | { 23 | type: 'input', 24 | name: 'scope', 25 | message: 'Scope? (Press enter to skip)', 26 | }, 27 | ]); 28 | 29 | const answers = exports.remapPromptsToAnswers(prompts); 30 | const { module_name } = answers; 31 | 32 | try { 33 | // Create the new directory 34 | const dest = path.join(pwd, module_name); 35 | const destSrc = path.join(dest, 'src'); 36 | mkdirp.sync(destSrc); 37 | 38 | // Generating the new files 39 | console.log(''); 40 | console.log('🗂', '', 'Generating files...'); 41 | exports.generateTemplateFiles(dest, answers); 42 | console.log(''); 43 | console.log('🚚', '', 'Installing dependencies...'); 44 | spawn.sync('npm', ['--prefix', dest, 'install']); 45 | console.log(''); 46 | console.log('✨', '', 'Success!'); 47 | console.log('Your new module is available at', dest); 48 | console.log(''); 49 | console.log('To start, run:'); 50 | console.log(`cd ./${module_name}`); 51 | console.log(''); 52 | console.log('Happy Coding!'); 53 | } catch (err) { 54 | console.log(''); 55 | console.log("Hmm! Zero wasn't able to generate a new module."); 56 | } 57 | }; 58 | 59 | exports.getTemplateFiles = () => { 60 | const templateDir = here('../templates'); 61 | const templateFilePath = path.join(templateDir, '/**/(*|.*)'); 62 | const templateFiles = glob.sync(templateFilePath); 63 | 64 | return templateFiles; 65 | }; 66 | 67 | exports.getTemplateProps = rawProps => { 68 | const { module_name, scope } = rawProps; 69 | const pkgName = scope ? `@${scope}/${module_name}` : module_name; 70 | 71 | return { 72 | name: module_name, 73 | scope, 74 | pkgName, 75 | zeroVersion: pkg.version, 76 | }; 77 | }; 78 | 79 | exports.generateTemplateFiles = (dest, rawProps) => { 80 | const templateFiles = exports.getTemplateFiles(); 81 | const props = exports.getTemplateProps(rawProps); 82 | 83 | const srcIndexDest = path.join(dest, 'src/index.js'); 84 | const srcPrettierDest = path.join(dest, '.prettierrc.js'); 85 | const srcESLintRc = path.join(dest, '.eslintrc'); 86 | 87 | templateFiles.forEach(file => { 88 | const fileContent = fs.readFileSync(file, 'utf8'); 89 | const compiled = template(fileContent)(props); 90 | const templateDestBase = file.split('/templates/')[1]; 91 | const templateDest = path.join(dest, templateDestBase); 92 | 93 | fs.writeFileSync(templateDest, compiled); 94 | console.log(`Generated ${templateDest}`); 95 | }); 96 | 97 | // Create the src/index.js file 98 | fs.writeFileSync(srcIndexDest, '// Happy Coding!'); 99 | console.log(`Generated ${srcIndexDest}`); 100 | 101 | // Create the src/.prettierrc.js file 102 | fs.writeFileSync( 103 | srcPrettierDest, 104 | "module.exports = require('@itsjonq/zero/prettier')", 105 | ); 106 | 107 | fs.writeFileSync( 108 | srcESLintRc, 109 | '{ "extends": "./node_modules/@itsjonq/zero/eslint.js" }', 110 | ); 111 | console.log(`Generated ${srcPrettierDest}`); 112 | }; 113 | 114 | exports.remapPromptsToAnswers = prompts => { 115 | return Object.keys(prompts).reduce((acc, key) => { 116 | let value = prompts[key]; 117 | if (key === 'scope') { 118 | value = value.replace('@', ''); 119 | } 120 | acc[key] = value; 121 | 122 | return acc; 123 | }, {}); 124 | }; 125 | 126 | exports.execNew(); 127 | -------------------------------------------------------------------------------- /src/scripts/pre-commit.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const spawn = require('cross-spawn'); 3 | const { isOptedIn, hasPkgProp, hasFile, resolveBin } = require('../utils'); 4 | 5 | const here = p => path.join(__dirname, p); 6 | const hereRelative = p => here(p).replace(process.cwd(), '.'); 7 | 8 | const args = process.argv.slice(2); 9 | 10 | const useBuiltInConfig = 11 | !args.includes('--config') && 12 | !hasFile('.lintstagedrc') && 13 | !hasFile('lint-staged.config.js') && 14 | !hasPkgProp('lint-staged'); 15 | 16 | const config = useBuiltInConfig 17 | ? ['--config', hereRelative('../config/lintstagedrc.js')] 18 | : []; 19 | 20 | const lintStagedResult = spawn.sync( 21 | resolveBin('lint-staged'), 22 | [...config, ...args], 23 | { stdio: 'inherit' }, 24 | ); 25 | 26 | if (lintStagedResult.status !== 0 || !isOptedIn('pre-commit')) { 27 | process.exit(lintStagedResult.status); 28 | } else { 29 | const validateResult = spawn.sync('npm', ['run', 'validate'], { 30 | stdio: 'inherit', 31 | }); 32 | 33 | process.exit(validateResult.status); 34 | } 35 | -------------------------------------------------------------------------------- /src/scripts/prestart.js: -------------------------------------------------------------------------------- 1 | const prestart = require('@itsjonq/prestart'); 2 | prestart.sync(); 3 | -------------------------------------------------------------------------------- /src/scripts/release.js: -------------------------------------------------------------------------------- 1 | require('np/source/cli'); 2 | -------------------------------------------------------------------------------- /src/scripts/setup/babel.js: -------------------------------------------------------------------------------- 1 | const { fromRoot, hasFile, writeFileToRoot } = require('../../utils'); 2 | 3 | const babelConfig = fromRoot('./.babelrc'); 4 | 5 | const setupBabel = () => { 6 | if (hasFile('./.babelrc')) { 7 | console.log(`.babelrc already exists at ${babelConfig}`); 8 | console.log('Cancelling Babel setup'); 9 | process.exit(0); 10 | } 11 | 12 | const content = '{ "presets": ["@itsjonq/zero/babel"] }'; 13 | 14 | console.log(`Generating ${babelConfig}...`); 15 | writeFileToRoot('./.babelrc', content); 16 | 17 | console.log(); 18 | console.log('Finished setting up Babel!'); 19 | }; 20 | 21 | setupBabel(); 22 | -------------------------------------------------------------------------------- /src/scripts/setup/eslint.js: -------------------------------------------------------------------------------- 1 | const { fromRoot, hasFile, writeFileToRoot } = require('../../utils'); 2 | 3 | const eslintConfig = fromRoot('./.eslintrc'); 4 | const eslintIgnore = fromRoot('./.eslintignore'); 5 | 6 | const setupESLint = () => { 7 | if (hasFile('./.eslintrc')) { 8 | console.log(`.eslintrc already exists at ${eslintConfig}`); 9 | console.log('Cancelling ESLint setup'); 10 | process.exit(0); 11 | } 12 | 13 | const content = '{ "extends": "./node_modules/@itsjonq/zero/eslint.js" }'; 14 | const ignoreContent = 'node_modules\ncoverage\ndist'; 15 | 16 | console.log(`Generating ${eslintConfig}...`); 17 | writeFileToRoot('./.eslintrc', content); 18 | 19 | console.log(`Generating ${eslintIgnore}...`); 20 | writeFileToRoot('./.eslintignore', ignoreContent); 21 | 22 | console.log(); 23 | console.log('Finished setting up ESLint!'); 24 | }; 25 | 26 | setupESLint(); 27 | -------------------------------------------------------------------------------- /src/scripts/test.js: -------------------------------------------------------------------------------- 1 | process.env.BABEL_ENV = 'test'; 2 | process.env.NODE_ENV = 'test'; 3 | 4 | // Force (Zero) babel to use @babel/plugin-transform-runtime 5 | process.env.BUILD_RUNTIME = true; 6 | 7 | // From create-react-app 8 | // Makes the script crash on unhandled rejections instead of silently 9 | // ignoring them. In the future, promise rejections that are not handled will 10 | // terminate the Node.js process with a non-zero exit code. 11 | process.on('unhandledRejection', (err) => { 12 | throw err; 13 | }); 14 | 15 | const jest = require('jest'); 16 | const isCI = require('is-ci'); 17 | const { hasPkgProp, parseEnv, hasFile } = require('../utils'); 18 | 19 | const args = process.argv.slice(2); 20 | 21 | const watch = 22 | !isCI && 23 | !parseEnv('SCRIPTS_PRE-COMMIT', false) && 24 | !args.includes('--no-watch') && 25 | !args.includes('--coverage') && 26 | !args.includes('--updateSnapshot') 27 | ? ['--watch'] 28 | : []; 29 | 30 | const config = 31 | !args.includes('--config') && 32 | !hasFile('jest.config.js') && 33 | !hasPkgProp('jest') 34 | ? ['--config', JSON.stringify(require('../config/jest.config'))] 35 | : []; 36 | 37 | jest.run([...config, ...watch, ...args]); 38 | -------------------------------------------------------------------------------- /src/scripts/typecheck.js: -------------------------------------------------------------------------------- 1 | const { tsConfigSrc, hasTsConfig } = require('../utils'); 2 | const { execTypeScript } = require('../exec/typescript'); 3 | 4 | const tsConfig = tsConfigSrc(); 5 | 6 | const check = async () => { 7 | if (!hasTsConfig()) { 8 | console.log(`Could not find ${tsConfig}`); 9 | return; 10 | } 11 | 12 | console.log('Type checking with TypeScript...'); 13 | console.log(`Loading ${tsConfig}...`); 14 | const result = await execTypeScript(['--noEmit']); 15 | 16 | console.log(); 17 | if (result === 1) { 18 | console.log('We noticed type issues :('); 19 | } else { 20 | console.log('No type issues found!'); 21 | } 22 | }; 23 | 24 | check(); 25 | -------------------------------------------------------------------------------- /src/scripts/validate.js: -------------------------------------------------------------------------------- 1 | const spawn = require('cross-spawn'); 2 | const { 3 | parseEnv, 4 | resolveBin, 5 | ifScript, 6 | getConcurrentlyArgs, 7 | } = require('../utils'); 8 | 9 | // pre-commit runs linting and tests on the relevant files 10 | // so those scripts don't need to be run if we're running 11 | // this in the context of a pre-commit hook. 12 | const preCommit = parseEnv('SCRIPTS_PRE-COMMIT', false); 13 | 14 | const validateScripts = process.argv[2]; 15 | 16 | const useDefaultScripts = typeof validateScripts !== 'string'; 17 | 18 | const scripts = useDefaultScripts 19 | ? { 20 | build: ifScript('build', 'npm run build --silent'), 21 | lint: preCommit ? null : ifScript('lint', 'npm run lint --silent'), 22 | test: preCommit 23 | ? null 24 | : ifScript('test', 'npm run test --silent -- --coverage'), 25 | } 26 | : validateScripts.split(',').reduce((scriptsToRun, name) => { 27 | scriptsToRun[name] = `npm run ${name} --silent`; 28 | return scriptsToRun; 29 | }, {}); 30 | 31 | const result = spawn.sync( 32 | resolveBin('concurrently'), 33 | getConcurrentlyArgs(scripts), 34 | { stdio: 'inherit' }, 35 | ); 36 | 37 | process.exit(result.status); 38 | -------------------------------------------------------------------------------- /src/templates/.babelrc: -------------------------------------------------------------------------------- 1 | { "presets": ["@itsjonq/zero/babel"] } 2 | -------------------------------------------------------------------------------- /src/templates/.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | coverage 3 | dist -------------------------------------------------------------------------------- /src/templates/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .cache 3 | .eslintcache 4 | .opt-in 5 | .opt-out 6 | coverage 7 | dist 8 | node_modules 9 | -------------------------------------------------------------------------------- /src/templates/.npmignore: -------------------------------------------------------------------------------- 1 | __fixtures__ 2 | __mocks__ 3 | __tests__ 4 | -------------------------------------------------------------------------------- /src/templates/.prettierignore: -------------------------------------------------------------------------------- 1 | package.json 2 | package-lock.json 3 | node_modules 4 | dist 5 | coverage -------------------------------------------------------------------------------- /src/templates/.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "lts/*" 4 | 5 | cache: 6 | directories: 7 | - node_modules 8 | 9 | install: 10 | - npm install 11 | 12 | script: 13 | - npm run validate 14 | 15 | branches: 16 | only: 17 | - master 18 | -------------------------------------------------------------------------------- /src/templates/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2019 Jon Quach 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/templates/README.md: -------------------------------------------------------------------------------- 1 | # <%= name %> 2 | -------------------------------------------------------------------------------- /src/templates/jest.config.js: -------------------------------------------------------------------------------- 1 | const jestConfig = require('@itsjonq/zero/jest') 2 | 3 | module.exports = Object.assign(jestConfig, { 4 | // your overrides here 5 | }) 6 | -------------------------------------------------------------------------------- /src/templates/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "<%= pkgName %>", 3 | "version": "0.0.0", 4 | "description": "Module generated with Zero", 5 | "main": "dist/cjs/index.js", 6 | "module": "dist/es/index.js", 7 | "sideEffects": false, 8 | "private": false, 9 | "scripts": { 10 | "prestart": "zero prestart", 11 | "build:es": "BUILD_FORMAT=es zero build -d dist/es", 12 | "build:cjs": "BUILD_FORMAT=cjs zero build -d dist/cjs", 13 | "build": "npm run build:cjs && npm run build:es -- --no-clean", 14 | "lint": "zero lint", 15 | "dev": "zero test", 16 | "test": "zero test --coverage", 17 | "test:coverage": "zero test --coverage", 18 | "format": "zero format", 19 | "validate": "zero validate", 20 | "release": "zero release", 21 | "version": "npm run build", 22 | "precommit": "zero pre-commit" 23 | }, 24 | "files": [ 25 | "dist", 26 | "README.md", 27 | "LICENSE" 28 | ], 29 | "author": "Jon Quach (https://jonquach.com)", 30 | "repository": { 31 | "type": "git", 32 | "url": "git+https://github.com/itsjonq/zero.git" 33 | }, 34 | "bugs": { 35 | "url": "https://github.com/itsjonq/zero/issues" 36 | }, 37 | "homepage": "https://github.com/itsjonq/zero#readme", 38 | "keywords": [], 39 | "license": "MIT", 40 | "devDependencies": { 41 | "@itsjonq/zero": "<%= zeroVersion %>" 42 | }, 43 | "publishConfig": { 44 | "access": "public" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/utils.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | 3 | const path = require('path'); 4 | 5 | const rimraf = require('rimraf'); 6 | 7 | const mkdirp = require('mkdirp'); 8 | 9 | const arrify = require('arrify'); 10 | 11 | const has = require('lodash.has'); 12 | 13 | const readPkgUp = require('read-pkg-up'); 14 | 15 | const which = require('which'); 16 | 17 | const cosmiconfig = require('cosmiconfig'); 18 | 19 | const { package: pkg, path: pkgPath } = readPkgUp.sync({ 20 | cwd: fs.realpathSync(process.cwd()), 21 | }); 22 | const appDirectory = path.dirname(pkgPath); 23 | 24 | function resolveZeroScripts() { 25 | if (pkg.name === '@itsjonq/zero') { 26 | return require.resolve('./').replace(process.cwd(), '.'); 27 | } 28 | 29 | return resolveBin('zero'); 30 | } // eslint-disable-next-line complexity 31 | 32 | function resolveBin( 33 | modName, 34 | { executable = modName, cwd = process.cwd() } = {}, 35 | ) { 36 | let pathFromWhich; 37 | 38 | try { 39 | pathFromWhich = fs.realpathSync(which.sync(executable)); 40 | if (pathFromWhich && pathFromWhich.includes('.CMD')) 41 | return pathFromWhich; 42 | } catch (_error) { 43 | // ignore _error 44 | } 45 | 46 | try { 47 | const modPkgPath = require.resolve(`${modName}/package.json`); 48 | 49 | const modPkgDir = path.dirname(modPkgPath); 50 | 51 | const { bin } = require(modPkgPath); 52 | 53 | const binPath = typeof bin === 'string' ? bin : bin[executable]; 54 | const fullPathToBin = path.join(modPkgDir, binPath); 55 | 56 | if (fullPathToBin === pathFromWhich) { 57 | return executable; 58 | } 59 | 60 | return fullPathToBin.replace(cwd, '.'); 61 | } catch (error) { 62 | if (pathFromWhich) { 63 | return executable; 64 | } 65 | 66 | throw error; 67 | } 68 | } 69 | 70 | const fromRoot = (...p) => path.join(appDirectory, ...p); 71 | 72 | const hasFile = (...p) => fs.existsSync(fromRoot(...p)); 73 | 74 | const ifFile = (files, t, f) => 75 | arrify(files).some((file) => hasFile(file)) ? t : f; 76 | 77 | const hasPkgProp = (props) => arrify(props).some((prop) => has(pkg, prop)); 78 | 79 | const hasPkgSubProp = (pkgProp) => (props) => 80 | hasPkgProp(arrify(props).map((p) => `${pkgProp}.${p}`)); 81 | 82 | const ifPkgSubProp = (pkgProp) => (props, t, f) => 83 | hasPkgSubProp(pkgProp)(props) ? t : f; 84 | 85 | const hasScript = hasPkgSubProp('scripts'); 86 | const hasPeerDep = hasPkgSubProp('peerDependencies'); 87 | const hasDep = hasPkgSubProp('dependencies'); 88 | const hasDevDep = hasPkgSubProp('devDependencies'); 89 | const hasAnyDep = (args) => 90 | [hasDep, hasDevDep, hasPeerDep].some((fn) => fn(args)); 91 | 92 | const ifPeerDep = ifPkgSubProp('peerDependencies'); 93 | const ifDep = ifPkgSubProp('dependencies'); 94 | const ifDevDep = ifPkgSubProp('devDependencies'); 95 | const ifAnyDep = (deps, t, f) => (hasAnyDep(arrify(deps)) ? t : f); 96 | const ifScript = ifPkgSubProp('scripts'); 97 | 98 | const hasTypescript = hasAnyDep('typescript') && hasFile('tsconfig.json'); 99 | const ifTypescript = (t, f) => (hasTypescript ? t : f); 100 | 101 | function parseEnv(name, def) { 102 | if (envIsSet(name)) { 103 | try { 104 | return JSON.parse(process.env[name]); 105 | } catch (err) { 106 | return process.env[name]; 107 | } 108 | } 109 | 110 | return def; 111 | } 112 | 113 | function envIsSet(name) { 114 | return ( 115 | process.env.hasOwnProperty(name) && 116 | process.env[name] && 117 | process.env[name] !== 'undefined' 118 | ); 119 | } 120 | 121 | function getConcurrentlyArgs(scripts, { killOthers = true } = {}) { 122 | const colors = [ 123 | 'bgBlue', 124 | 'bgGreen', 125 | 'bgMagenta', 126 | 'bgCyan', 127 | 'bgWhite', 128 | 'bgRed', 129 | 'bgBlack', 130 | 'bgYellow', 131 | ]; 132 | scripts = Object.entries(scripts).reduce((all, [name, script]) => { 133 | if (script) { 134 | all[name] = script; 135 | } 136 | 137 | return all; 138 | }, {}); 139 | const prefixColors = Object.keys(scripts).reduce((pColors, _s, i) => pColors.concat([`${colors[i % colors.length]}.bold.reset`]), []).join(','); // prettier-ignore 140 | 141 | return [ 142 | killOthers ? '--kill-others-on-fail' : null, 143 | '--prefix', 144 | '[{name}]', 145 | '--names', 146 | Object.keys(scripts).join(','), 147 | '--prefix-colors', 148 | prefixColors, 149 | ...Object.values(scripts).map((s) => JSON.stringify(s)), // stringify escapes quotes ✨ 150 | ].filter(Boolean); 151 | } 152 | 153 | function isOptedOut(key, t = true, f = false) { 154 | if (!fs.existsSync(fromRoot('.opt-out'))) { 155 | return f; 156 | } 157 | 158 | const contents = fs.readFileSync(fromRoot('.opt-out'), 'utf-8'); 159 | return contents.includes(key) ? t : f; 160 | } 161 | 162 | function isOptedIn(key, t = true, f = false) { 163 | if (!fs.existsSync(fromRoot('.opt-in'))) { 164 | return f; 165 | } 166 | 167 | const contents = fs.readFileSync(fromRoot('.opt-in'), 'utf-8'); 168 | return contents.includes(key) ? t : f; 169 | } 170 | 171 | function uniq(arr) { 172 | return Array.from(new Set(arr)); 173 | } 174 | 175 | function writeExtraEntry(name, { cjs, esm }, clean = true) { 176 | if (clean) { 177 | rimraf.sync(fromRoot(name)); 178 | } 179 | 180 | mkdirp.sync(fromRoot(name)); 181 | const pkgJson = fromRoot(`${name}/package.json`); 182 | const entryDir = fromRoot(name); 183 | fs.writeFileSync( 184 | pkgJson, 185 | JSON.stringify( 186 | { 187 | main: path.relative(entryDir, cjs), 188 | 'jsnext:main': path.relative(entryDir, esm), 189 | module: path.relative(entryDir, esm), 190 | }, 191 | null, 192 | 2, 193 | ), 194 | ); 195 | } 196 | 197 | function hasLocalConfig(moduleName, searchOptions = {}) { 198 | const explorer = cosmiconfig(moduleName, searchOptions); 199 | const result = explorer.searchSync(pkgPath); 200 | return result !== null; 201 | } 202 | 203 | /** 204 | * Custom utils 205 | */ 206 | const here = (p) => path.join(__dirname, p); 207 | const there = (p) => path.resolve(process.cwd(), p); 208 | 209 | const tsConfigSrc = () => there('./tsconfig.json'); 210 | const hasTsConfig = () => fs.existsSync(tsConfigSrc()); 211 | 212 | const writeFileToRoot = (p, content, ...args) => { 213 | return fs.writeFileSync(fromRoot(p), content, ...args); 214 | }; 215 | 216 | module.exports = { 217 | appDirectory, 218 | envIsSet, 219 | fromRoot, 220 | getConcurrentlyArgs, 221 | hasDep, 222 | hasFile, 223 | hasLocalConfig, 224 | hasPkgProp, 225 | hasScript, 226 | hasTsConfig, 227 | hasTypescript, 228 | here, 229 | ifAnyDep, 230 | ifDep, 231 | ifDevDep, 232 | ifFile, 233 | ifPeerDep, 234 | ifScript, 235 | isOptedIn, 236 | isOptedOut, 237 | ifTypescript, 238 | parseEnv, 239 | pkg, 240 | resolveBin, 241 | resolveZeroScripts, 242 | uniq, 243 | there, 244 | tsConfigSrc, 245 | writeExtraEntry, 246 | writeFileToRoot, 247 | }; 248 | --------------------------------------------------------------------------------