├── .editorconfig ├── .eslintignore ├── .eslintrc.js ├── .gitattributes ├── .github └── workflows │ ├── ci.yml │ └── publish.yml ├── .gitignore ├── .mocharc.js ├── .npmrc ├── .vscode └── launch.json ├── LICENSE ├── README.md ├── appveyor.yml ├── bin └── index.js ├── config └── ember-cli-update.json ├── package-lock.json ├── package.json ├── renovate.json ├── src ├── args.js ├── debug.js ├── eject.js ├── get-package-versions.js ├── get-project-type.js ├── get-start-and-end-commands.js ├── get-tag-version.js ├── index.js ├── run-sync.js └── utils.js └── test ├── acceptance └── index-test.js ├── fixtures ├── codemod │ ├── before │ │ └── my-app │ │ │ ├── package.json │ │ │ └── src │ │ │ └── index.js │ ├── latest-node │ │ └── my-app │ │ │ ├── package.json │ │ │ └── src │ │ │ └── index.js │ └── min-node │ │ └── my-app │ │ ├── package.json │ │ └── src │ │ └── index.js ├── ejected │ ├── local │ │ └── my-app │ │ │ ├── .gitignore │ │ │ ├── README.md │ │ │ ├── config │ │ │ ├── env.js │ │ │ ├── jest │ │ │ │ ├── CSSStub.js │ │ │ │ └── FileStub.js │ │ │ ├── paths.js │ │ │ ├── polyfills.js │ │ │ ├── webpack.config.dev.js │ │ │ └── webpack.config.prod.js │ │ │ ├── package.json │ │ │ ├── public │ │ │ ├── favicon.ico │ │ │ └── index.html │ │ │ ├── scripts │ │ │ ├── build.js │ │ │ ├── start.js │ │ │ └── test.js │ │ │ └── src │ │ │ ├── App.css │ │ │ ├── App.js │ │ │ ├── App.test.js │ │ │ ├── index.css │ │ │ ├── index.js │ │ │ └── logo.svg │ └── merge │ │ └── my-app │ │ ├── .gitignore │ │ ├── README.md │ │ ├── config │ │ ├── env.js │ │ ├── jest │ │ │ ├── cssTransform.js │ │ │ └── fileTransform.js │ │ ├── paths.js │ │ ├── webpack.config.dev.js │ │ ├── webpack.config.prod.js │ │ └── webpackDevServer.config.js │ │ ├── package.json │ │ ├── public │ │ ├── favicon.ico │ │ ├── index.html │ │ └── manifest.json │ │ ├── scripts │ │ ├── build.js │ │ ├── start.js │ │ └── test.js │ │ └── src │ │ ├── App.css │ │ ├── App.js │ │ ├── App.test.js │ │ ├── index.css │ │ ├── index.js │ │ ├── logo.svg │ │ └── serviceWorker.js ├── normal │ ├── local │ │ └── my-app │ │ │ ├── .gitignore │ │ │ ├── README.md │ │ │ ├── package.json │ │ │ ├── public │ │ │ ├── favicon.ico │ │ │ └── index.html │ │ │ └── src │ │ │ ├── App.css │ │ │ ├── App.js │ │ │ ├── App.test.js │ │ │ ├── index.css │ │ │ ├── index.js │ │ │ └── logo.svg │ ├── merge │ │ └── my-app │ │ │ ├── .gitignore │ │ │ ├── README.md │ │ │ ├── package.json │ │ │ ├── public │ │ │ ├── favicon.ico │ │ │ ├── index.html │ │ │ └── manifest.json │ │ │ └── src │ │ │ ├── App.css │ │ │ ├── App.js │ │ │ ├── App.test.js │ │ │ ├── index.css │ │ │ ├── index.js │ │ │ ├── logo.svg │ │ │ └── serviceWorker.js │ └── reset │ │ └── my-app │ │ ├── .gitignore │ │ ├── README.md │ │ ├── package.json │ │ ├── public │ │ ├── favicon.ico │ │ ├── index.html │ │ └── manifest.json │ │ └── src │ │ ├── App.css │ │ ├── App.js │ │ ├── App.test.js │ │ ├── index.css │ │ ├── index.js │ │ ├── logo.svg │ │ └── serviceWorker.js └── package-json │ ├── malformed │ └── my-app │ │ └── package.json │ ├── missing │ └── my-app │ │ └── .gitkeep │ └── non-create-react-app │ └── my-app │ └── package.json ├── helpers ├── assertions.js ├── chai.js └── mocha.js ├── integration └── index-test.js └── unit ├── get-project-type-test.js └── get-start-and-end-commands-test.js /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | trim_trailing_whitespace = true 5 | insert_final_newline = true 6 | indent_style = space 7 | indent_size = 2 8 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | !.* 2 | 3 | /node_modules/ 4 | 5 | /test/fixtures/ 6 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | root: true, 5 | parserOptions: { 6 | ecmaVersion: 2022 7 | }, 8 | env: { 9 | es6: true 10 | }, 11 | extends: [ 12 | 'sane-node' 13 | ], 14 | overrides: [ 15 | { 16 | files: ['bin/*.js'], 17 | rules: { 18 | 'no-console': 'off' 19 | } 20 | }, 21 | { 22 | files: [ 23 | 'test/**/*-test.js' 24 | ], 25 | env: { 26 | mocha: true 27 | }, 28 | plugins: [ 29 | 'mocha' 30 | ], 31 | extends: [ 32 | 'plugin:mocha/recommended' 33 | ], 34 | rules: { 35 | 'mocha/no-exclusive-tests': 'error', 36 | 'mocha/no-empty-description': 'off', 37 | 'mocha/no-setup-in-describe': 'off' 38 | } 39 | } 40 | ] 41 | }; 42 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | # allow manual running 5 | workflow_dispatch: 6 | push: 7 | branches: 8 | - main 9 | pull_request: 10 | 11 | jobs: 12 | lint: 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - uses: actions/checkout@v4 17 | - uses: actions/setup-node@v4 18 | with: 19 | node-version: 18 20 | 21 | - run: npm ci 22 | - run: npm run lint 23 | 24 | test: 25 | needs: lint 26 | 27 | strategy: 28 | matrix: 29 | os: 30 | - ubuntu-latest 31 | node: 32 | - 18 33 | 34 | - node 35 | 36 | runs-on: ${{ matrix.os }} 37 | 38 | steps: 39 | - uses: actions/checkout@v4 40 | - uses: actions/setup-node@v4 41 | with: 42 | node-version: ${{ matrix.node }} 43 | 44 | - run: which npx 45 | # don't accidentally use the global npx 46 | - run: rm $(which npx) 47 | # sometimes this exists 48 | - run: rm /usr/local/bin/npx || true 49 | # test that we can't find it 50 | - run: "! which npx" 51 | 52 | - run: npm ci 53 | - run: npm test 54 | env: 55 | DEBUG: create-react-app-updater,boilerplate-update,git-diff-apply 56 | 57 | ember-cli-update: 58 | needs: test 59 | if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository 60 | 61 | runs-on: ubuntu-latest 62 | 63 | steps: 64 | - uses: actions/checkout@v4 65 | with: 66 | ref: ${{ github.head_ref }} 67 | token: ${{ secrets.GitHubToken }} 68 | - uses: actions/setup-node@v4 69 | with: 70 | node-version: 18 71 | 72 | - uses: kellyselden/ember-cli-update-action@v6 73 | with: 74 | autofix_command: npm run lint -- --fix 75 | ignore_to: true 76 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish 2 | 3 | on: 4 | push: 5 | tags: v[0-9]+.[0-9]+.[0-9]+ 6 | 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | 11 | steps: 12 | - uses: actions/checkout@v4 13 | - uses: actions/setup-node@v4 14 | with: 15 | node-version: 18 16 | 17 | registry-url: 'https://registry.npmjs.org' 18 | 19 | - run: npm publish 20 | env: 21 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules/ 2 | -------------------------------------------------------------------------------- /.mocharc.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | spec: ['test/!(fixtures)/**/*-test.js'], 5 | retries: 2 6 | }; 7 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | engine-strict=true 2 | # ignore-scripts=true This causes `npm test` to not run (node 14, npm 6, on CI) 3 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible Node.js debug attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "node", 9 | "request": "launch", 10 | "name": "Mocha Tests", 11 | "program": "${workspaceRoot}/node_modules/mocha/bin/_mocha", 12 | "args": [ 13 | "--timeout", 14 | "999999", 15 | "--colors", 16 | "test/!(fixtures)/**/*-test.js" 17 | ], 18 | "env": { 19 | "DEBUG": "create-react-app-updater,boilerplate-update,git-diff-apply" 20 | }, 21 | "internalConsoleOptions": "openOnSessionStart" 22 | } 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Kelly Selden 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 all 13 | 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 THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # create-react-app-updater 2 | 3 | [![npm version](https://badge.fury.io/js/create-react-app-updater.svg)](https://badge.fury.io/js/create-react-app-updater) 4 | [![Build status](https://ci.appveyor.com/api/projects/status/y8ua3584brlpcrpb/branch/main?svg=true)](https://ci.appveyor.com/project/kellyselden/create-react-app-updater/branch/main) 5 | 6 | Update [Create React App](https://facebook.github.io/create-react-app/) projects 7 | 8 | Fetches list of codemods and instructions from [create-react-app-updater-codemods-manifest](https://github.com/kellyselden/create-react-app-updater-codemods-manifest) 9 | 10 | This is a thin wrapper of [boilerplate-update](https://github.com/kellyselden/boilerplate-update). 11 | 12 | Ported from [ember-cli-update](https://github.com/ember-cli/ember-cli-update) 13 | 14 | https://www.youtube.com/watch?v=pgS3-F0sXeM 15 | 16 | ## Installation 17 | 18 | As a global executable: 19 | 20 | `npm install -g create-react-app-updater` 21 | 22 | ## Usage 23 | 24 | Make sure your git working directory is clean before updating. 25 | 26 | Inside your project directory, if you installed globally run 27 | 28 | `create-react-app-updater` 29 | 30 | or the shorter 31 | 32 | `cra-update` 33 | 34 | If you want to use [npx](https://www.npmjs.com/package/npx) run 35 | 36 | `npx create-react-app-updater` 37 | 38 | It applies a diff of the changes from the latest version to your project. It will only modify the files if there are changes between your project's version and the latest version, and it will only change the section necessary, not the entire file. 39 | 40 | You will probably encounter merge conflicts, in which the default behavior is to let you resolve conflicts on your own. You can supply the `--resolve-conflicts` option to run your system's git merge tool if any conflicts are found. 41 | 42 | This tool can also run codemods for you. The option `--run-codemods` will figure out what codemods apply to your current version of React, and download and run them for you. 43 | 44 | ## Examples 45 | 46 | (These examples assume you are using the global command.) 47 | 48 | To update to the latest version of Create React App: 49 | 50 | ``` 51 | cra-update 52 | ``` 53 | 54 | To update to a certain version of Create React App: 55 | 56 | ``` 57 | cra-update --to 2.1.1 58 | ``` 59 | 60 | To run codemods: 61 | 62 | (This should be run after running the normal update shown above, and after you've resolved any conflicts.) 63 | 64 | ``` 65 | cra-update --run-codemods 66 | ``` 67 | 68 | ## Options 69 | 70 | | Option | Description | Type | Examples | Default | 71 | |---|---|---|---|---| 72 | | --from | Use a starting version that is different than what is in your package.json | String | "2.9.1" | | 73 | | --to | Update to a version that isn\'t latest | String | "2.14.1" "~2.15" "latest" "beta" | "latest" | 74 | | --resolve-conflicts | Automatically run git mergetool if conflicts found | Boolean | | false | 75 | | --run-codemods | Run codemods to help update your code | Boolean | | false | 76 | | --reset | Reset your code to the default boilerplate at the new version | Boolean | | false | 77 | | --compare-only | Show the changes between different versions without updating | Boolean | | false | 78 | | --stats-only | Show all calculated values regarding your project | Boolean | | false | 79 | | --list-codemods | List available codemods | Boolean | | false | 80 | 81 | ## Hints 82 | 83 | Need help using `git mergetool`? Here are some starting points: 84 | 85 | * https://git-scm.com/docs/git-mergetool 86 | * https://gist.github.com/karenyyng/f19ff75c60f18b4b8149 87 | 88 | If you made a mistake during the update/conflict resolution, run these commands to undo everything and get you back to before the update: 89 | 90 | ``` 91 | git reset --hard 92 | git clean -f 93 | ``` 94 | 95 | If you notice ".orig" files lying around after a merge and don't want that behavior, run `git config --global mergetool.keepBackup false`. 96 | 97 | To avoid being prompted "Hit return to start merge resolution tool (vimdiff):" for every conflict, set a merge tool like `git config --global merge.tool "vimdiff"`. 98 | 99 | If you run into an error like `error: unrecognized input`, you may need to update your git config color option like `git config --global color.ui auto`. 100 | 101 | ## Troubleshooting 102 | 103 | If you are getting an error or unexpected results, running the command with the debug flag: 104 | 105 | * Unix (global):   `DEBUG=create-react-app-updater,boilerplate-update,git-diff-apply create-react-app-updater` 106 | * Windows (global):   `set DEBUG=create-react-app-updater,boilerplate-update,git-diff-apply && create-react-app-updater` 107 | * Unix (npx):   `DEBUG=create-react-app-updater,boilerplate-update,git-diff-apply npx create-react-app-updater` 108 | * Windows (npx):   `set DEBUG=create-react-app-updater,boilerplate-update,git-diff-apply && npx create-react-app-updater` 109 | 110 | will give you more detailed logging. 111 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | environment: 2 | matrix: 3 | - nodejs_version: "18" 4 | 5 | - nodejs_version: "" 6 | DEBUG: "create-react-app-updater,boilerplate-update,git-diff-apply" 7 | 8 | branches: 9 | only: 10 | - main 11 | 12 | # Fix line endings in Windows. (runs before repo cloning) 13 | init: 14 | - git config --global core.autocrlf true 15 | 16 | # Install scripts. (runs after repo cloning) 17 | install: 18 | - ps: Install-Product node $env:nodejs_version 19 | - where npx 20 | # don't accidentally use the global npx 21 | - ps: rm "C:\Program Files\nodejs\npx*" 22 | - ps: rm C:\Users\appveyor\AppData\Roaming\npm\npx* 23 | # test that we can't find it 24 | - ps: if (gcm npx) { exit } 25 | - npm ci 26 | 27 | # Post-install test scripts. 28 | test_script: 29 | - npm test 30 | 31 | # http://help.appveyor.com/discussions/questions/1310-delete-cache 32 | cache: 33 | - node_modules -> package-lock.json 34 | 35 | # Don't actually build. 36 | build: off 37 | -------------------------------------------------------------------------------- /bin/index.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 'use strict'; 3 | 4 | const createReactAppUpdater = require('../src'); 5 | const args = require('../src/args'); 6 | 7 | const { argv } = require('yargs') 8 | .options(args); 9 | 10 | (async() => { 11 | try { 12 | let message = await createReactAppUpdater(argv); 13 | 14 | if (message) { 15 | console.log(message); 16 | } 17 | } catch (err) { 18 | console.error(err); 19 | } 20 | })(); 21 | -------------------------------------------------------------------------------- /config/ember-cli-update.json: -------------------------------------------------------------------------------- 1 | { 2 | "schemaVersion": "1.0.0", 3 | "packages": [ 4 | { 5 | "name": "standard-node-template", 6 | "version": "6.0.0", 7 | "blueprints": [ 8 | { 9 | "name": "standard-node-template", 10 | "isBaseBlueprint": true 11 | } 12 | ] 13 | }, 14 | { 15 | "name": "@kellyselden/node-template", 16 | "version": "5.1.0", 17 | "blueprints": [ 18 | { 19 | "name": "@kellyselden/node-template", 20 | "options": [ 21 | "--repo-slug=kellyselden/create-react-app-updater", 22 | "--github-actions", 23 | "--appveyor=y8ua3584brlpcrpb", 24 | "--renovate" 25 | ] 26 | } 27 | ] 28 | } 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "create-react-app-updater", 3 | "version": "0.13.3", 4 | "description": "Update Create React App projects", 5 | "main": "src", 6 | "bin": { 7 | "create-react-app-updater": "bin/index.js", 8 | "cra-update": "bin/index.js" 9 | }, 10 | "scripts": { 11 | "lint": "eslint . --ext js,json", 12 | "test": "mocha" 13 | }, 14 | "repository": { 15 | "type": "git", 16 | "url": "git+ssh://git@gitlab.com/kellyselden/create-react-app-updater.git" 17 | }, 18 | "author": "Kelly Selden", 19 | "license": "MIT", 20 | "bugs": { 21 | "url": "https://gitlab.com/kellyselden/create-react-app-updater/issues" 22 | }, 23 | "homepage": "https://gitlab.com/kellyselden/create-react-app-updater#readme", 24 | "engines": { 25 | "node": ">=18.12" 26 | }, 27 | "files": [ 28 | "bin", 29 | "src" 30 | ], 31 | "dependencies": { 32 | "boilerplate-update": "^5.0.0", 33 | "debug": "^4.1.1", 34 | "execa": "^9.0.0", 35 | "npm": "^10.0.0", 36 | "p-map": "^7.0.0", 37 | "pacote": "^20.0.0", 38 | "resolve": "^1.10.0", 39 | "semver": "^7.0.0", 40 | "yargs": "^17.0.0" 41 | }, 42 | "devDependencies": { 43 | "@kellyselden/node-template": "5.1.0", 44 | "chai": "^4.4.1", 45 | "eslint": "^8.57.0", 46 | "eslint-config-sane": "^1.0.2", 47 | "eslint-config-sane-node": "^2.0.0", 48 | "eslint-plugin-json-files": "^4.2.0", 49 | "eslint-plugin-mocha": "^10.4.3", 50 | "eslint-plugin-n": "^17.9.0", 51 | "eslint-plugin-prefer-let": "^4.0.0", 52 | "git-fixtures": "^8.0.0", 53 | "mocha": "^10.7.0", 54 | "mocha-helpers": "^9.0.1", 55 | "renovate-config-standard": "2.1.2", 56 | "sinon": "^19.0.0", 57 | "standard-node-template": "6.0.0" 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "standard" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /src/args.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | 'from': { 5 | type: 'string', 6 | description: 'Use a starting version that is different than what is in your package.json ("2.9.1")' 7 | }, 8 | 'to': { 9 | type: 'string', 10 | default: 'latest', 11 | description: 'Update to a version that isn\'t latest ("2.14.1", "~2.15", "latest", "beta")' 12 | }, 13 | 'resolve-conflicts': { 14 | type: 'boolean', 15 | default: false, 16 | description: 'Automatically run git mergetool if conflicts found' 17 | }, 18 | 'run-codemods': { 19 | type: 'boolean', 20 | default: false, 21 | description: 'Run codemods to help update your code' 22 | }, 23 | 'reset': { 24 | type: 'boolean', 25 | default: false, 26 | description: 'Reset your code to the default boilerplate at the new version' 27 | }, 28 | 'stats-only': { 29 | type: 'boolean', 30 | default: false, 31 | description: 'Show all calculated values regarding your project' 32 | }, 33 | 'list-codemods': { 34 | type: 'boolean', 35 | default: false, 36 | description: 'List available codemods' 37 | } 38 | }; 39 | -------------------------------------------------------------------------------- /src/debug.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = require('debug')(require('../package').name); 4 | -------------------------------------------------------------------------------- /src/eject.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const semver = require('semver'); 4 | const { spawn } = require('child_process'); 5 | 6 | async function eject({ 7 | reactScriptsVersion, 8 | cwd 9 | }) { 10 | let ps = spawn('node', [ 11 | 'node_modules/react-scripts/bin/react-scripts.js', 12 | 'eject' 13 | ], { 14 | cwd 15 | }); 16 | 17 | ps.stdin.write('y\n'); 18 | if (semver.lte(reactScriptsVersion, '0.8.1')) { 19 | ps.stdin.end(); 20 | } 21 | 22 | await new Promise((resolve, reject) => { 23 | ps.on('error', reject); 24 | ps.on('exit', resolve); 25 | }); 26 | } 27 | 28 | module.exports = eject; 29 | -------------------------------------------------------------------------------- /src/get-package-versions.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const semver = require('semver'); 4 | const pacote = require('pacote'); 5 | const getVersions = require('boilerplate-update/src/get-versions'); 6 | const getTimes = require('boilerplate-update/src/get-times'); 7 | const getVersionAsOf = require('boilerplate-update/src/get-version-as-of'); 8 | 9 | async function crawl({ 10 | parentVersions, 11 | childVersions, 12 | childVersion, 13 | parentPackageName, 14 | childPackageName 15 | }) { 16 | let parentVersion; 17 | 18 | let minChildVersion = semver.minSatisfying(childVersions, childVersion); 19 | 20 | let sortedParentVersions = parentVersions.sort((a, b) => { 21 | return semver.lt(a, b) ? 1 : -1; 22 | }); 23 | 24 | // eslint-disable-next-line prefer-let/prefer-let 25 | const { default: pMap } = await import('p-map'); 26 | 27 | await pMap(sortedParentVersions, async _parentVersion => { 28 | if (parentVersion) { 29 | return; 30 | } 31 | 32 | let manifest; 33 | 34 | try { 35 | manifest = await pacote.manifest(`${parentPackageName}@${_parentVersion}`); 36 | } catch (err) { 37 | if (err.code === 'ETARGET') { 38 | return; 39 | } else { 40 | throw err; 41 | } 42 | } 43 | 44 | if (parentVersion) { 45 | return; 46 | } 47 | 48 | let _childVersion = manifest.dependencies[childPackageName]; 49 | 50 | if (_childVersion === childVersion) { 51 | parentVersion = _parentVersion; 52 | } else if (!semver.prerelease(_childVersion)) { 53 | let _minChildVersion = semver.minSatisfying(childVersions, _childVersion); 54 | if (semver.lte(_minChildVersion, minChildVersion)) { 55 | parentVersion = _parentVersion; 56 | } 57 | } 58 | }, { concurrency: 5 }); 59 | 60 | return parentVersion; 61 | } 62 | 63 | module.exports = async function getPackageVersion({ 64 | dependencies, 65 | devDependencies 66 | }, projectType) { 67 | let [ 68 | createReactAppTimes, 69 | reactScriptsTimes 70 | ] = await Promise.all([ 71 | getTimes('create-react-app'), 72 | getTimes('react-scripts') 73 | ]); 74 | 75 | let reactScriptsVersions = Object.keys(reactScriptsTimes); 76 | 77 | let allDeps = Object.assign({}, dependencies, devDependencies); 78 | 79 | let reactScriptsVersion; 80 | 81 | if (projectType === 'ejected') { 82 | let reactDevUtilsVersions = await getVersions('react-dev-utils'); 83 | 84 | let reactDevUtilsVersion = allDeps['react-dev-utils']; 85 | 86 | reactScriptsVersion = await crawl({ 87 | parentVersions: reactScriptsVersions, 88 | childVersions: reactDevUtilsVersions, 89 | childVersion: reactDevUtilsVersion, 90 | parentPackageName: 'react-scripts', 91 | childPackageName: 'react-dev-utils' 92 | }); 93 | } else { 94 | reactScriptsVersion = semver.minSatisfying(reactScriptsVersions, allDeps['react-scripts']); 95 | } 96 | 97 | if (!reactScriptsVersion) { 98 | throw 'React Scripts version could not be determined'; 99 | } 100 | 101 | let reactScriptsTime = reactScriptsTimes[reactScriptsVersion]; 102 | 103 | let createReactAppVersion = getVersionAsOf(createReactAppTimes, reactScriptsTime); 104 | 105 | if (!createReactAppVersion) { 106 | throw 'Create React App version could not be determined'; 107 | } 108 | 109 | return { 110 | 'create-react-app': createReactAppVersion, 111 | 'react-scripts': reactScriptsVersion 112 | }; 113 | }; 114 | -------------------------------------------------------------------------------- /src/get-project-type.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function getProjectType({ 4 | dependencies, 5 | devDependencies 6 | }) { 7 | function checkForDep(packageName) { 8 | return allDeps[packageName] !== undefined; 9 | } 10 | 11 | let allDeps = Object.assign({}, dependencies, devDependencies); 12 | 13 | let isNormal = checkForDep('react-scripts'); 14 | 15 | if (isNormal) { 16 | return 'normal'; 17 | } 18 | 19 | let isEjected = checkForDep('react-dev-utils'); 20 | 21 | if (isEjected) { 22 | return 'ejected'; 23 | } 24 | 25 | throw 'Create React App project type could not be determined'; 26 | }; 27 | -------------------------------------------------------------------------------- /src/get-start-and-end-commands.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | const utils = require('./utils'); 5 | const getTimes = require('boilerplate-update/src/get-times'); 6 | const getVersionAsOf = require('boilerplate-update/src/get-version-as-of'); 7 | 8 | module.exports = function getStartAndEndCommands({ 9 | projectName, 10 | projectType, 11 | createReactAppStartVersion, 12 | reactScriptsStartVersion, 13 | startTime, 14 | createReactAppEndVersion, 15 | reactScriptsEndVersion, 16 | endTime 17 | }) { 18 | // test 19 | // require('./run-sync')(['npm', 'i', `${packageName}@1.0.0`, '--no-save', '--no-package-lock']); 20 | // require('./run-sync')(['npm', 'i', '-g', `${packageName}@2.1.1`]); 21 | 22 | return { 23 | projectName, 24 | projectType, 25 | packageName: 'create-react-app', 26 | createProjectFromCache, 27 | createProjectFromRemote, 28 | mutatePackageJson, 29 | startOptions: { 30 | packageVersion: createReactAppStartVersion, 31 | reactScriptsVersion: reactScriptsStartVersion, 32 | time: startTime 33 | }, 34 | endOptions: { 35 | packageVersion: createReactAppEndVersion, 36 | reactScriptsVersion: reactScriptsEndVersion, 37 | time: endTime 38 | } 39 | }; 40 | }; 41 | 42 | function createProjectFromCache({ 43 | packageRoot, 44 | options 45 | }) { 46 | return async function createProject(cwd) { 47 | await utils.execaNode(path.join(packageRoot, 'index.js'), [ 48 | options.projectName, 49 | '--scripts-version', 50 | options.reactScriptsVersion 51 | ], { 52 | cwd 53 | }); 54 | 55 | return await postCreateProject({ 56 | cwd, 57 | options 58 | }); 59 | }; 60 | } 61 | 62 | function createProjectFromRemote({ 63 | options 64 | }) { 65 | return async function createProject(cwd) { 66 | let execa = await import('execa'); 67 | 68 | // create-react-app doesn't work well with async npx 69 | utils.npxSync.call(execa, [`create-react-app@${options.packageVersion}`, options.projectName, '--scripts-version', options.reactScriptsVersion], { cwd }); 70 | 71 | return await postCreateProject({ 72 | cwd, 73 | options 74 | }); 75 | }; 76 | } 77 | 78 | async function postCreateProject({ 79 | cwd, 80 | options: { 81 | projectName, 82 | projectType, 83 | reactScriptsVersion 84 | } 85 | }) { 86 | let appPath = path.join(cwd, projectName); 87 | 88 | if (projectType === 'ejected') { 89 | await utils.eject({ 90 | cwd: appPath, 91 | reactScriptsVersion 92 | }); 93 | } 94 | 95 | return appPath; 96 | } 97 | 98 | function mutatePackageJson({ 99 | projectType, 100 | reactScriptsVersion, 101 | time 102 | }) { 103 | return async function mutatePackageJson(pkg) { 104 | if (projectType === 'normal') { 105 | let newVersion = `^${reactScriptsVersion}`; 106 | let packageName = 'react-scripts'; 107 | if (pkg.dependencies[packageName]) { 108 | // v2.1.1 109 | pkg.dependencies[packageName] = newVersion; 110 | } else { 111 | // v1.0.0 112 | pkg.devDependencies[packageName] = newVersion; 113 | } 114 | } 115 | 116 | // eslint-disable-next-line prefer-let/prefer-let 117 | const { default: pMap } = await import('p-map'); 118 | 119 | await pMap(['react', 'react-dom'], async packageName => { 120 | let times = await getTimes(packageName); 121 | let version = getVersionAsOf(times, time); 122 | pkg.dependencies[packageName] = `^${version}`; 123 | }); 124 | }; 125 | } 126 | -------------------------------------------------------------------------------- /src/get-tag-version.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const _getTagVersion = require('boilerplate-update/src/get-tag-version'); 4 | 5 | module.exports = function getTagVersion(versions) { 6 | return async function getTagVersion(range) { 7 | return await _getTagVersion({ 8 | range, 9 | versions, 10 | packageName: 'create-react-app', 11 | distTags: [ 12 | 'latest', 13 | 'next', 14 | 'canary' 15 | ] 16 | }); 17 | }; 18 | }; 19 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const getProjectType = require('./get-project-type'); 4 | const getPackageVersions = require('./get-package-versions'); 5 | const _getTagVersion = require('./get-tag-version'); 6 | const boilerplateUpdate = require('boilerplate-update'); 7 | const getStartAndEndCommands = require('./get-start-and-end-commands'); 8 | const getTimes = require('boilerplate-update/src/get-times'); 9 | const getVersionAsOf = require('boilerplate-update/src/get-version-as-of'); 10 | 11 | function getVersionAsOfMargin(times, time, margin = 0) { 12 | time = new Date(new Date(time).getTime() + margin); 13 | return getVersionAsOf(times, time); 14 | } 15 | 16 | module.exports = async function createReactAppUpdater({ 17 | cwd = process.cwd(), 18 | from, 19 | to, 20 | resolveConflicts, 21 | runCodemods, 22 | reset, 23 | statsOnly, 24 | listCodemods 25 | }) { 26 | return await (await boilerplateUpdate({ 27 | cwd, 28 | projectOptions: ({ packageJson }) => [getProjectType(packageJson)], 29 | resolveConflicts, 30 | reset, 31 | statsOnly, 32 | listCodemods, 33 | runCodemods, 34 | codemodsSource: 'https://github.com/kellyselden/create-react-app-updater-codemods-manifest.git#semver:1', 35 | createCustomDiff: true, 36 | mergeOptions: async function mergeOptions({ 37 | packageJson, 38 | projectOptions: [projectType] 39 | }) { 40 | let [ 41 | createReactAppTimes, 42 | reactScriptsTimes 43 | ] = await Promise.all([ 44 | getTimes('create-react-app'), 45 | getTimes('react-scripts') 46 | ]); 47 | 48 | let createReactAppVersions = Object.keys(createReactAppTimes); 49 | let getTagVersion = _getTagVersion(createReactAppVersions); 50 | let margin = 24 * 60 * 60e3; 51 | 52 | let startVersion; 53 | let reactScriptsStartVersion; 54 | let startTime; 55 | if (!reset) { 56 | if (from) { 57 | startVersion = await getTagVersion(from); 58 | startTime = createReactAppTimes[startVersion]; 59 | reactScriptsStartVersion = getVersionAsOfMargin(reactScriptsTimes, startTime, margin); 60 | } else { 61 | let { 62 | 'create-react-app': createReactAppVersion, 63 | 'react-scripts': reactScriptsVersion 64 | } = await getPackageVersions(packageJson, projectType); 65 | 66 | startVersion = createReactAppVersion; 67 | startTime = reactScriptsTimes[reactScriptsVersion]; 68 | reactScriptsStartVersion = reactScriptsVersion; 69 | } 70 | } 71 | 72 | let endVersion = await getTagVersion(to); 73 | let endTime = createReactAppTimes[endVersion]; 74 | let reactScriptsEndVersion = getVersionAsOfMargin(reactScriptsTimes, endTime, margin); 75 | 76 | return { 77 | startVersion, 78 | endVersion, 79 | customDiffOptions: getStartAndEndCommands({ 80 | projectName: packageJson.name, 81 | projectType, 82 | createReactAppStartVersion: startVersion, 83 | reactScriptsStartVersion, 84 | startTime, 85 | createReactAppEndVersion: endVersion, 86 | reactScriptsEndVersion, 87 | endTime 88 | }) 89 | }; 90 | } 91 | })).promise; 92 | }; 93 | -------------------------------------------------------------------------------- /src/run-sync.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const debug = require('./debug'); 4 | 5 | module.exports = function runSync() { 6 | debug(...arguments); 7 | let { stdout } = this.execaSync(...arguments); 8 | debug(stdout); 9 | return stdout; 10 | }; 11 | -------------------------------------------------------------------------------- /src/utils.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | 5 | module.exports.eject = require('./eject'); 6 | 7 | module.exports.npxSync = function npxSync(args, options) { 8 | // npm ERR! cb.apply is not a function 9 | // return require('./run-sync')('npx', args, { 10 | // preferLocal: true, 11 | // localDir: __dirname, 12 | // ...options 13 | // }); 14 | 15 | return require('./run-sync').call(this, 'node', [path.join(path.dirname(require.resolve('npm')), 'bin/npx-cli.js'), ...args], options); 16 | }; 17 | 18 | module.exports.execaNode = async function execaNode() { 19 | let { execaNode } = await import('execa'); 20 | 21 | return execaNode(...arguments); 22 | }; 23 | -------------------------------------------------------------------------------- /test/acceptance/index-test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { describe, it } = require('../helpers/mocha'); 4 | const { expect } = require('../helpers/chai'); 5 | const { 6 | buildTmp, 7 | processBin, 8 | fixtureCompare: _fixtureCompare 9 | } = require('git-fixtures'); 10 | const { 11 | assertNormalUpdate, 12 | assertNoUnstaged, 13 | assertCodemodRan 14 | } = require('../helpers/assertions'); 15 | 16 | describe(function() { 17 | this.timeout(3 * 60e3); 18 | 19 | let tmpPath; 20 | 21 | async function merge({ 22 | fixturesPath, 23 | runCodemods, 24 | subDir = '', 25 | commitMessage 26 | }) { 27 | tmpPath = await buildTmp({ 28 | fixturesPath, 29 | commitMessage, 30 | subDir 31 | }); 32 | 33 | let args = [ 34 | '--to=2.1.1' 35 | ]; 36 | if (runCodemods) { 37 | args = [ 38 | '--run-codemods' 39 | ]; 40 | } 41 | 42 | return processBin({ 43 | binFile: 'index', 44 | args, 45 | cwd: tmpPath, 46 | commitMessage, 47 | expect 48 | }); 49 | } 50 | 51 | function fixtureCompare({ 52 | mergeFixtures 53 | }) { 54 | let actual = tmpPath; 55 | let expected = mergeFixtures; 56 | 57 | _fixtureCompare({ 58 | expect, 59 | actual, 60 | expected 61 | }); 62 | } 63 | 64 | it('works', async function() { 65 | let { 66 | status 67 | } = await (await merge({ 68 | fixturesPath: 'test/fixtures/normal/local', 69 | commitMessage: 'my-app' 70 | })).promise; 71 | 72 | fixtureCompare({ 73 | mergeFixtures: 'test/fixtures/normal/merge/my-app' 74 | }); 75 | 76 | assertNormalUpdate(status); 77 | assertNoUnstaged(status); 78 | }); 79 | 80 | it('runs codemods', async function() { 81 | let { 82 | ps, 83 | promise 84 | } = await merge({ 85 | fixturesPath: 'test/fixtures/codemod/before', 86 | commitMessage: 'my-app', 87 | runCodemods: true 88 | }); 89 | 90 | ps.stdout.on('data', data => { 91 | let str = data.toString(); 92 | if (str.includes('These codemods apply to your project.')) { 93 | ps.stdin.write('a\n'); 94 | } 95 | }); 96 | 97 | let { 98 | status 99 | } = await promise; 100 | 101 | // file is indeterminent between OS's, so ignore 102 | // fs.removeSync(path.join(tmpPath, 'MODULE_REPORT.md')); 103 | 104 | let mergeFixtures = 'test/fixtures/codemod/latest-node/my-app'; 105 | if (process.env.NODE_LTS) { 106 | mergeFixtures = 'test/fixtures/codemod/min-node/my-app'; 107 | } 108 | 109 | fixtureCompare({ 110 | mergeFixtures 111 | }); 112 | 113 | assertNoUnstaged(status); 114 | assertCodemodRan(status); 115 | }); 116 | }); 117 | -------------------------------------------------------------------------------- /test/fixtures/codemod/before/my-app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "react": "^16.6.0", 4 | "react-scripts": "^2.1.1" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /test/fixtures/codemod/before/my-app/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import './index.css'; 4 | import App from './App'; 5 | 6 | ReactDOM.render(React.createElement(App), document.getElementById('root')); 7 | 8 | class MyComponent extends React.Component { 9 | unstable_handleError() { 10 | this.setState({ error: true }); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /test/fixtures/codemod/latest-node/my-app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "react": "^16.6.0", 4 | "react-scripts": "^2.1.1" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /test/fixtures/codemod/latest-node/my-app/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import './index.css'; 4 | import App from './App'; 5 | 6 | ReactDOM.render(, document.getElementById('root')); 7 | 8 | class MyComponent extends React.Component { 9 | componentDidCatch() { 10 | this.setState({ error: true }); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /test/fixtures/codemod/min-node/my-app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "react": "^16.6.0", 4 | "react-scripts": "^2.1.1" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /test/fixtures/codemod/min-node/my-app/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import './index.css'; 4 | import App from './App'; 5 | 6 | ReactDOM.render(, document.getElementById('root')); 7 | 8 | class MyComponent extends React.Component { 9 | componentDidCatch() { 10 | this.setState({ error: true }); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /test/fixtures/ejected/local/my-app/.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | node_modules 5 | 6 | # testing 7 | coverage 8 | 9 | # production 10 | build 11 | 12 | # misc 13 | .DS_Store 14 | .env 15 | npm-debug.log 16 | -------------------------------------------------------------------------------- /test/fixtures/ejected/local/my-app/config/env.js: -------------------------------------------------------------------------------- 1 | // Grab NODE_ENV and REACT_APP_* environment variables and prepare them to be 2 | // injected into the application via DefinePlugin in Webpack configuration. 3 | 4 | var REACT_APP = /^REACT_APP_/i; 5 | 6 | function getClientEnvironment(publicUrl) { 7 | var processEnv = Object 8 | .keys(process.env) 9 | .filter(key => REACT_APP.test(key)) 10 | .reduce((env, key) => { 11 | env[key] = JSON.stringify(process.env[key]); 12 | return env; 13 | }, { 14 | // Useful for determining whether we’re running in production mode. 15 | // Most importantly, it switches React into the correct mode. 16 | 'NODE_ENV': JSON.stringify( 17 | process.env.NODE_ENV || 'development' 18 | ), 19 | // Useful for resolving the correct path to static assets in `public`. 20 | // For example, . 21 | // This should only be used as an escape hatch. Normally you would put 22 | // images into the `src` and `import` them in code to get their paths. 23 | 'PUBLIC_URL': JSON.stringify(publicUrl) 24 | }); 25 | return {'process.env': processEnv}; 26 | } 27 | 28 | module.exports = getClientEnvironment; 29 | -------------------------------------------------------------------------------- /test/fixtures/ejected/local/my-app/config/jest/CSSStub.js: -------------------------------------------------------------------------------- 1 | module.exports = {}; 2 | -------------------------------------------------------------------------------- /test/fixtures/ejected/local/my-app/config/jest/FileStub.js: -------------------------------------------------------------------------------- 1 | module.exports = "test-file-stub"; 2 | -------------------------------------------------------------------------------- /test/fixtures/ejected/local/my-app/config/paths.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var fs = require('fs'); 3 | 4 | // Make sure any symlinks in the project folder are resolved: 5 | // https://github.com/facebookincubator/create-react-app/issues/637 6 | var appDirectory = fs.realpathSync(process.cwd()); 7 | function resolveApp(relativePath) { 8 | return path.resolve(appDirectory, relativePath); 9 | } 10 | 11 | // We support resolving modules according to `NODE_PATH`. 12 | // This lets you use absolute paths in imports inside large monorepos: 13 | // https://github.com/facebookincubator/create-react-app/issues/253. 14 | 15 | // It works similar to `NODE_PATH` in Node itself: 16 | // https://nodejs.org/api/modules.html#modules_loading_from_the_global_folders 17 | 18 | // We will export `nodePaths` as an array of absolute paths. 19 | // It will then be used by Webpack configs. 20 | // Jest doesn’t need this because it already handles `NODE_PATH` out of the box. 21 | 22 | var nodePaths = (process.env.NODE_PATH || '') 23 | .split(process.platform === 'win32' ? ';' : ':') 24 | .filter(Boolean) 25 | .map(resolveApp); 26 | 27 | // config after eject: we're in ./config/ 28 | module.exports = { 29 | appBuild: resolveApp('build'), 30 | appPublic: resolveApp('public'), 31 | appHtml: resolveApp('public/index.html'), 32 | appIndexJs: resolveApp('src/index.js'), 33 | appPackageJson: resolveApp('package.json'), 34 | appSrc: resolveApp('src'), 35 | yarnLockFile: resolveApp('yarn.lock'), 36 | testsSetup: resolveApp('src/setupTests.js'), 37 | appNodeModules: resolveApp('node_modules'), 38 | ownNodeModules: resolveApp('node_modules'), 39 | nodePaths: nodePaths 40 | }; 41 | -------------------------------------------------------------------------------- /test/fixtures/ejected/local/my-app/config/polyfills.js: -------------------------------------------------------------------------------- 1 | if (typeof Promise === 'undefined') { 2 | // Rejection tracking prevents a common issue where React gets into an 3 | // inconsistent state due to an error, but it gets swallowed by a Promise, 4 | // and the user has no idea what causes React's erratic future behavior. 5 | require('promise/lib/rejection-tracking').enable(); 6 | window.Promise = require('promise/lib/es6-extensions.js'); 7 | } 8 | 9 | // fetch() polyfill for making API calls. 10 | require('whatwg-fetch'); 11 | 12 | // Object.assign() is commonly used with React. 13 | // It will use the native implementation if it's present and isn't buggy. 14 | Object.assign = require('object-assign'); 15 | -------------------------------------------------------------------------------- /test/fixtures/ejected/local/my-app/config/webpack.config.dev.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var autoprefixer = require('autoprefixer'); 3 | var webpack = require('webpack'); 4 | var HtmlWebpackPlugin = require('html-webpack-plugin'); 5 | var CaseSensitivePathsPlugin = require('case-sensitive-paths-webpack-plugin'); 6 | var InterpolateHtmlPlugin = require('react-dev-utils/InterpolateHtmlPlugin'); 7 | var WatchMissingNodeModulesPlugin = require('react-dev-utils/WatchMissingNodeModulesPlugin'); 8 | var getClientEnvironment = require('./env'); 9 | var paths = require('./paths'); 10 | 11 | // Webpack uses `publicPath` to determine where the app is being served from. 12 | // In development, we always serve from the root. This makes config easier. 13 | var publicPath = '/'; 14 | // `publicUrl` is just like `publicPath`, but we will provide it to our app 15 | // as %PUBLIC_URL% in `index.html` and `process.env.PUBLIC_URL` in JavaScript. 16 | // Omit trailing slash as %PUBLIC_PATH%/xyz looks better than %PUBLIC_PATH%xyz. 17 | var publicUrl = ''; 18 | // Get environment variables to inject into our app. 19 | var env = getClientEnvironment(publicUrl); 20 | 21 | // This is the development configuration. 22 | // It is focused on developer experience and fast rebuilds. 23 | // The production configuration is different and lives in a separate file. 24 | module.exports = { 25 | // You may want 'eval' instead if you prefer to see the compiled output in DevTools. 26 | // See the discussion in https://github.com/facebookincubator/create-react-app/issues/343. 27 | devtool: 'cheap-module-source-map', 28 | // These are the "entry points" to our application. 29 | // This means they will be the "root" imports that are included in JS bundle. 30 | // The first two entry points enable "hot" CSS and auto-refreshes for JS. 31 | entry: [ 32 | // Include an alternative client for WebpackDevServer. A client's job is to 33 | // connect to WebpackDevServer by a socket and get notified about changes. 34 | // When you save a file, the client will either apply hot updates (in case 35 | // of CSS changes), or refresh the page (in case of JS changes). When you 36 | // make a syntax error, this client will display a syntax error overlay. 37 | // Note: instead of the default WebpackDevServer client, we use a custom one 38 | // to bring better experience for Create React App users. You can replace 39 | // the line below with these two lines if you prefer the stock client: 40 | // require.resolve('webpack-dev-server/client') + '?/', 41 | // require.resolve('webpack/hot/dev-server'), 42 | require.resolve('react-dev-utils/webpackHotDevClient'), 43 | // We ship a few polyfills by default: 44 | require.resolve('./polyfills'), 45 | // Finally, this is your app's code: 46 | paths.appIndexJs 47 | // We include the app code last so that if there is a runtime error during 48 | // initialization, it doesn't blow up the WebpackDevServer client, and 49 | // changing JS code would still trigger a refresh. 50 | ], 51 | output: { 52 | // Next line is not used in dev but WebpackDevServer crashes without it: 53 | path: paths.appBuild, 54 | // Add /* filename */ comments to generated require()s in the output. 55 | pathinfo: true, 56 | // This does not produce a real file. It's just the virtual path that is 57 | // served by WebpackDevServer in development. This is the JS bundle 58 | // containing code from all our entry points, and the Webpack runtime. 59 | filename: 'static/js/bundle.js', 60 | // This is the URL that app is served from. We use "/" in development. 61 | publicPath: publicPath 62 | }, 63 | resolve: { 64 | // This allows you to set a fallback for where Webpack should look for modules. 65 | // We read `NODE_PATH` environment variable in `paths.js` and pass paths here. 66 | // We use `fallback` instead of `root` because we want `node_modules` to "win" 67 | // if there any conflicts. This matches Node resolution mechanism. 68 | // https://github.com/facebookincubator/create-react-app/issues/253 69 | fallback: paths.nodePaths, 70 | // These are the reasonable defaults supported by the Node ecosystem. 71 | // We also include JSX as a common component filename extension to support 72 | // some tools, although we do not recommend using it, see: 73 | // https://github.com/facebookincubator/create-react-app/issues/290 74 | extensions: ['.js', '.json', '.jsx', ''], 75 | alias: { 76 | // Support React Native Web 77 | // https://www.smashingmagazine.com/2016/08/a-glimpse-into-the-future-with-react-native-for-web/ 78 | 'react-native': 'react-native-web' 79 | } 80 | }, 81 | 82 | module: { 83 | // First, run the linter. 84 | // It's important to do this before Babel processes the JS. 85 | preLoaders: [ 86 | { 87 | test: /\.(js|jsx)$/, 88 | loader: 'eslint', 89 | include: paths.appSrc, 90 | } 91 | ], 92 | loaders: [ 93 | // Default loader: load all assets that are not handled 94 | // by other loaders with the url loader. 95 | // Note: This list needs to be updated with every change of extensions 96 | // the other loaders match. 97 | // E.g., when adding a loader for a new supported file extension, 98 | // we need to add the supported extension to this loader too. 99 | // Add one new line in `exclude` for each loader. 100 | // 101 | // "file" loader makes sure those assets get served by WebpackDevServer. 102 | // When you `import` an asset, you get its (virtual) filename. 103 | // In production, they would get copied to the `build` folder. 104 | // "url" loader works like "file" loader except that it embeds assets 105 | // smaller than specified limit in bytes as data URLs to avoid requests. 106 | // A missing `test` is equivalent to a match. 107 | { 108 | exclude: [ 109 | /\.html$/, 110 | /\.(js|jsx)$/, 111 | /\.css$/, 112 | /\.json$/ 113 | ], 114 | loader: 'url', 115 | query: { 116 | limit: 10000, 117 | name: 'static/media/[name].[hash:8].[ext]' 118 | } 119 | }, 120 | // Process JS with Babel. 121 | { 122 | test: /\.(js|jsx)$/, 123 | include: paths.appSrc, 124 | loader: 'babel', 125 | query: { 126 | 127 | // This is a feature of `babel-loader` for webpack (not Babel itself). 128 | // It enables caching results in ./node_modules/.cache/babel-loader/ 129 | // directory for faster rebuilds. 130 | cacheDirectory: true 131 | } 132 | }, 133 | // "postcss" loader applies autoprefixer to our CSS. 134 | // "css" loader resolves paths in CSS and adds assets as dependencies. 135 | // "style" loader turns CSS into JS modules that inject