├── .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 | [](https://badge.fury.io/js/create-react-app-updater)
4 | [](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