├── .eslintrc ├── .gitattributes ├── .github └── workflows │ ├── deploy-docs.yml │ ├── deploy-project.yml │ └── lint.yml ├── .gitignore ├── .prettierrc.json ├── .yarnrc ├── LICENSE ├── README.md ├── config ├── deploy │ └── nginx.conf ├── env.js ├── getHttpsConfig.js ├── jest │ ├── cssTransform.js │ └── fileTransform.js ├── modules.js ├── paths.js ├── pnpTs.js ├── webpack │ └── webpack.config.js └── webpackDevServer.config.js ├── docs ├── .gitignore ├── README.md ├── babel.config.js ├── docs │ ├── JudgeHost.md │ ├── additions.md │ ├── configuration.md │ ├── frontend.md │ ├── judgeHostApi.md │ ├── judgeHostDevelop.md │ ├── judgeServerApi.md │ ├── judgeServerDevelop.md │ ├── moreServer.md │ ├── quickStart.md │ ├── submission.md │ └── upload.md ├── docusaurus.config.js ├── package.json ├── sidebars.js ├── src │ ├── css │ │ └── custom.css │ └── pages │ │ └── styles.module.css ├── static │ ├── .nojekyll │ └── img │ │ └── favicon.ico └── yarn.lock ├── help └── rules.md ├── lintmdrc.json ├── package.json ├── public ├── favicon.ico ├── index.html ├── logo │ ├── logo-text-with-version.svg │ ├── logo-text.svg │ └── logo.svg ├── manifest.json └── robots.txt ├── scripts ├── build.js ├── locale.js ├── start.js ├── test.js └── upload.js ├── src ├── App.tsx ├── assets │ └── images │ │ ├── code-typing.svg │ │ ├── common-background.png │ │ └── login.jpg ├── common │ ├── enumerations.ts │ ├── judgeCondition.ts │ └── programLanguage.ts ├── components │ ├── betterMarkdown │ │ ├── BetterMarkdown.tsx │ │ └── mathInline.ts │ ├── charts │ │ ├── ColumnChart.tsx │ │ ├── LineChart.tsx │ │ └── PieChart.tsx │ ├── codeBlock │ │ ├── CodeBlock.tsx │ │ ├── childCmp │ │ │ └── CodeTool.tsx │ │ └── codeBlock.module.scss │ ├── codeEditor │ │ ├── CodeEditor.tsx │ │ └── codeEditor.module.scss │ ├── commonTitle │ │ ├── CommonTitle.tsx │ │ └── commonTitle.module.scss │ ├── conditionTag │ │ ├── ConditionTag.tsx │ │ └── conditionTag.module.scss │ ├── descriptionItem │ │ ├── DescriptionItem.tsx │ │ └── descriptionItem.module.scss │ ├── editorTip │ │ ├── editorTip.module.scss │ │ └── editorTip.tsx │ ├── inputEditor │ │ ├── InputEditor.tsx │ │ └── inputEditor.module.scss │ ├── judgeHostTable │ │ └── JudgeHostTable.tsx │ ├── judgeResultCount │ │ └── JudgeResultCount.tsx │ ├── loading │ │ └── Loading.tsx │ ├── localContext │ │ └── LocalContext.tsx │ ├── loginForm │ │ ├── LoginForm.tsx │ │ └── loginForm.module.scss │ ├── markdownEditor │ │ ├── MarkdownEditor.tsx │ │ └── markdownEditor.module.scss │ ├── mathBlock │ │ └── MathBlock.tsx │ ├── mathFormula │ │ └── MathFormula.tsx │ ├── noticeModal │ │ ├── NoticeModal.tsx │ │ └── noticeModal.module.scss │ ├── noticeTable │ │ ├── NoticeTable.tsx │ │ └── noticeTable.module.scss │ ├── problemSetTable │ │ └── ProblemSetTable.tsx │ ├── problemTable │ │ └── ProblemTable.tsx │ ├── registerForm │ │ ├── RegisterForm.tsx │ │ └── registerForm.module.scss │ ├── scoreBoardTable │ │ ├── ScoreBoardTable.tsx │ │ └── scoreBoardTable.module.scss │ ├── showTestCase │ │ └── ShowTestCase.tsx │ ├── submissionCount │ │ └── SubmissionCount.tsx │ ├── submissionDrawer │ │ ├── SubmissionDrawer.tsx │ │ └── submissionDrawer.module.scss │ ├── submitToolBar │ │ └── SubmitToolBar.tsx │ ├── tagGroup │ │ └── TagGroup.tsx │ ├── testCaseList │ │ ├── TestCaseList.tsx │ │ └── testCaseList.module.scss │ ├── testCaseTable │ │ └── TestCaseTable.tsx │ ├── userCard │ │ ├── UserCard.tsx │ │ └── userCard.module.scss │ ├── userGroupTable │ │ └── UserGroupTable.tsx │ ├── userTable │ │ └── UserTable.tsx │ └── userTag │ │ ├── UserTag.tsx │ │ ├── childCmp │ │ ├── ProblemItems.tsx │ │ └── UserTagMenu.tsx │ │ └── userTag.module.scss ├── config │ ├── code.ts │ └── config.ts ├── hooks │ ├── pagination.ts │ └── userInfo.ts ├── index.scss ├── index.tsx ├── layout │ ├── cms │ │ ├── CMSLayout.tsx │ │ └── childCmp │ │ │ ├── Breadcrumb.tsx │ │ │ ├── CMSHeader.tsx │ │ │ ├── SideBar.tsx │ │ │ └── WithBreadCrump.tsx │ ├── common │ │ ├── Common.tsx │ │ └── childCmp │ │ │ └── CommonMenu.tsx │ └── layout.module.scss ├── locale │ ├── en-US.ts │ ├── index.ts │ └── zh-CN.ts ├── models │ ├── UserGroup.ts │ ├── common.ts │ ├── judgeHost.ts │ ├── notice.ts │ ├── pagination.ts │ ├── permission.ts │ ├── problem.ts │ ├── problemSet.ts │ ├── submission.ts │ └── user.ts ├── network │ ├── common.ts │ ├── interceptor.ts │ ├── judgeHostRequest.ts │ ├── noticeRequest.ts │ ├── permissionRequest.ts │ ├── problemRequests.ts │ ├── problemSetRequest.ts │ ├── request.ts │ ├── submissionRequest.ts │ ├── userGroupRequest.ts │ └── userRequest.ts ├── pages │ ├── cms │ │ ├── dashboard │ │ │ ├── Dashboard.tsx │ │ │ ├── childCmp │ │ │ │ ├── ChartGroup.tsx │ │ │ │ ├── HeadCardGroup.tsx │ │ │ │ ├── HeadCardItem.tsx │ │ │ │ └── TableGroup.tsx │ │ │ └── dashboard.module.scss │ │ ├── judgeHostInspect │ │ │ ├── JudgeHostInspect.tsx │ │ │ ├── childCmp │ │ │ │ ├── BasicInfo.tsx │ │ │ │ ├── CurrentCondition.tsx │ │ │ │ └── Operations.tsx │ │ │ └── judgeHostInspect.module.scss │ │ ├── judgeHostManage │ │ │ ├── JudgeHostManage.tsx │ │ │ └── childCmp │ │ │ │ └── JudgeHostEditModal.tsx │ │ ├── problemEdit │ │ │ ├── ProblemEdit.tsx │ │ │ ├── childCmp │ │ │ │ ├── TestCaseModal.tsx │ │ │ │ └── problemEditor │ │ │ │ │ ├── ProblemEditor.tsx │ │ │ │ │ └── childCmp │ │ │ │ │ ├── BasicInfoForm.tsx │ │ │ │ │ ├── DangerZoneForm.tsx │ │ │ │ │ └── LimitationForm.tsx │ │ │ └── problemEdit.module.scss │ │ ├── problemManage │ │ │ ├── ProblemManage.tsx │ │ │ └── childCmp │ │ │ │ └── CreateProblemModal.tsx │ │ ├── problemSetEdit │ │ │ ├── ProblemSetEdit.tsx │ │ │ ├── childCmp │ │ │ │ ├── AddProblem.tsx │ │ │ │ ├── BasicInfoEditor.tsx │ │ │ │ └── ProblemSetEditor.tsx │ │ │ └── problemSetEdit.module.scss │ │ ├── problemSetManage │ │ │ ├── ProblemSetManage.tsx │ │ │ ├── childCmp │ │ │ │ └── ProblemSetToolBar.tsx │ │ │ └── problemSetManage.module.scss │ │ ├── settings │ │ │ ├── Settings.tsx │ │ │ ├── childCmp │ │ │ │ ├── CommonSettings.tsx │ │ │ │ └── DangerSettings.tsx │ │ │ └── settings.module.scss │ │ ├── userGroupManage │ │ │ ├── UserGroupManage.tsx │ │ │ └── childCmp │ │ │ │ ├── AuthorizeModal.tsx │ │ │ │ └── UserGroupEditModal.tsx │ │ └── userManage │ │ │ ├── UserManage.tsx │ │ │ └── childCmp │ │ │ ├── AllocateUserGroupsModal.tsx │ │ │ ├── CreateUserModal.tsx │ │ │ ├── UserEditModal.tsx │ │ │ └── UserManageToolBar.tsx │ ├── common │ │ ├── basicResult │ │ │ ├── BasicResult.tsx │ │ │ └── basicResult.module.scss │ │ ├── discussion │ │ │ └── Discussion.tsx │ │ ├── home │ │ │ ├── Home.tsx │ │ │ ├── childCmp │ │ │ │ ├── HomeContent.tsx │ │ │ │ ├── QuickStart.tsx │ │ │ │ └── SideItem.tsx │ │ │ └── home.module.scss │ │ ├── landing │ │ │ ├── Landing.tsx │ │ │ ├── childCmp │ │ │ │ ├── Feature.tsx │ │ │ │ ├── FeatureItem.tsx │ │ │ │ ├── LandingData.tsx │ │ │ │ ├── LandingFooter.tsx │ │ │ │ ├── LandingHeader.tsx │ │ │ │ └── MainPart.tsx │ │ │ └── landing.module.scss │ │ ├── login │ │ │ ├── Login.tsx │ │ │ └── loginPage.module.scss │ │ ├── problemHome │ │ │ ├── ProblemHome.tsx │ │ │ ├── childCmp │ │ │ │ ├── LanguageSelector.tsx │ │ │ │ ├── LanguageTip.tsx │ │ │ │ └── RouteSelector.tsx │ │ │ └── problemHome.module.scss │ │ ├── problemSetCount │ │ │ ├── ProblemSetCount.tsx │ │ │ ├── childCmp │ │ │ │ └── ProblemSetTimeLine.tsx │ │ │ └── problemSetCount.module.scss │ │ ├── problemSetHome │ │ │ ├── ProblemSetHome.tsx │ │ │ ├── childCmp │ │ │ │ └── ProblemSetDescription.tsx │ │ │ └── problemSetHome.module.scss │ │ ├── problemSetProblems │ │ │ ├── ProblemSetProblems.tsx │ │ │ └── problemSetProblem.module.scss │ │ ├── problemSets │ │ │ ├── ProblemSets.tsx │ │ │ └── problemSets.module.scss │ │ ├── problems │ │ │ ├── Problems.tsx │ │ │ └── problems.module.scss │ │ ├── profile │ │ │ ├── Profile.tsx │ │ │ ├── childCmp │ │ │ │ └── ProfileCount.tsx │ │ │ └── profile.module.scss │ │ ├── ranking │ │ │ └── Ranking.tsx │ │ ├── scoreBoard │ │ │ ├── ScoreBoard.tsx │ │ │ └── scoreBoard.module.scss │ │ ├── solution │ │ │ └── Solution.tsx │ │ └── submissionInspect │ │ │ ├── childCmp │ │ │ ├── SubmissionDetailModal.tsx │ │ │ └── SubmissionTable.tsx │ │ │ ├── submissionInspect.module.scss │ │ │ └── submissionInspect.tsx │ ├── index.scss │ └── index.tsx ├── react-app-env.d.ts ├── router │ ├── MyRouter.tsx │ ├── cmsJudgeHostMenu.ts │ ├── cmsMenu.ts │ ├── cmsProblemMenu.ts │ ├── cmsUserMenu.ts │ ├── commonMenu.ts │ ├── config.ts │ └── mainMenu.ts ├── store │ ├── action.ts │ ├── constants.ts │ ├── index.ts │ └── reducer.ts ├── styles │ ├── normalize.scss │ └── public.scss └── utils │ ├── chart.ts │ ├── dataPersistence.ts │ ├── dateTime.ts │ ├── dom.ts │ ├── getValue.ts │ ├── loadable.ts │ ├── markdown.ts │ ├── math.ts │ ├── regex.ts │ ├── route.ts │ └── string.ts ├── tsconfig.json └── yarn.lock /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "react-app", 3 | "rules": { 4 | "jsx-a11y/anchor-is-valid": "off", 5 | "quotes": [ 6 | "warn", 7 | "single" 8 | ], 9 | "no-console": "off" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | docs/* linguist-vendored=true 2 | config/* linguist-vendored=true -------------------------------------------------------------------------------- /.github/workflows/deploy-docs.yml: -------------------------------------------------------------------------------- 1 | name: deploy docs 2 | on: 3 | push: 4 | branches: 5 | - docs 6 | jobs: 7 | deploy-docs: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v2 11 | 12 | - name: Setup node.js environment 13 | uses: actions/setup-node@v2.1.2 14 | with: 15 | node-version: '14' 16 | 17 | - name: Install and build docs 18 | run: | 19 | cd ./docs 20 | yarn install 21 | yarn docusaurus build 22 | 23 | - name: Deploy by ssh 24 | uses: easingthemes/ssh-deploy@v2.1.5 25 | env: 26 | SSH_PRIVATE_KEY: ${{ secrets.SSH_ACCESS_TOKEN }} 27 | ARGS: "-avz --delete" 28 | SOURCE: "./docs/build/" 29 | REMOTE_HOST: "47.106.202.255" 30 | REMOTE_USER: "root" 31 | TARGET: "/home/oj-docs-deploy" 32 | -------------------------------------------------------------------------------- /.github/workflows/deploy-project.yml: -------------------------------------------------------------------------------- 1 | name: deploy project 2 | on: 3 | push: 4 | branches: 5 | - dev-for-outsourcing 6 | jobs: 7 | deploy-docs: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v2 11 | 12 | - name: Setup node.js environment 13 | uses: actions/setup-node@v2.1.2 14 | with: 15 | node-version: '14' 16 | 17 | - name: Install and build project 18 | run: | 19 | yarn install 20 | yarn buildWithPrefix 21 | 22 | - name: Upload project to CDN 23 | run: | 24 | node scripts/upload.js ${{ secrets.QINIU_ACCESS_KEY }} ${{ secrets.QINIU_SECRET_KEY }} 25 | 26 | - name: Deploy by ssh 27 | uses: easingthemes/ssh-deploy@v2.1.5 28 | env: 29 | SSH_PRIVATE_KEY: ${{ secrets.SSH_ACCESS_TOKEN }} 30 | ARGS: "-avz --delete" 31 | SOURCE: "./build" 32 | REMOTE_HOST: "47.99.245.139" 33 | REMOTE_USER: "root" 34 | TARGET: "/home/oj/website/build/" 35 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: Lint Code 2 | 3 | on: 4 | [ pull_request ] 5 | 6 | jobs: 7 | lint: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v2 11 | 12 | - name: Setup node.js environment 13 | uses: actions/setup-node@v2.1.2 14 | with: 15 | node-version: '14' 16 | 17 | - name: Install modules 18 | run: yarn install 19 | 20 | - name: Run eslint 21 | run: yarn lint 22 | 23 | - name: Run markdown lint 24 | run: yarn lint:md 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | 25 | /.idea 26 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "tabWidth": 2, 3 | "semi": false, 4 | "singleQuote": true, 5 | "jsxBracketSameLine": true 6 | } 7 | -------------------------------------------------------------------------------- /.yarnrc: -------------------------------------------------------------------------------- 1 | registry "https://registry.npm.taobao.org" 2 | sass_binary_site https://npm.taobao.org/mirrors/node-sass/ 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 YuZhanglong 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 | -------------------------------------------------------------------------------- /config/deploy/nginx.conf: -------------------------------------------------------------------------------- 1 | # File: nginx.conf 2 | # Description: 项目nginx配置文件 3 | # Created: 2020-8-27 20:10:33 4 | # Author: yuzhanglong 5 | # Email: yuzl1123@163.com 6 | 7 | server{ 8 | # 端口号 9 | listen 80; 10 | 11 | # 网站文件根目录 12 | root /home/web/build; 13 | 14 | # 开启gzip压缩 15 | gzip on; 16 | gzip_min_length 1k; 17 | gzip_buffers 4 16k; 18 | gzip_comp_level 2; 19 | gzip_types text/plain application/javascript application/x-javascript text/css application/xml text/javascript application/x-httpd-php image/jpeg image/gif image/png; 20 | gzip_vary off; 21 | gzip_disable "MSIE [1-6]\."; 22 | 23 | # 支持browser路由 24 | location / { 25 | try_files $uri $uri/ /index.html; 26 | } 27 | } -------------------------------------------------------------------------------- /config/getHttpsConfig.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | const fs = require('fs'); 4 | const path = require('path'); 5 | const crypto = require('crypto'); 6 | const chalk = require('react-dev-utils/chalk'); 7 | const paths = require('./paths'); 8 | 9 | // Ensure the certificate and key provided are valid and if not 10 | // throw an easy to debug error 11 | function validateKeyAndCerts({cert, key, keyFile, crtFile}) { 12 | let encrypted; 13 | try { 14 | // publicEncrypt will throw an error with an invalid cert 15 | encrypted = crypto.publicEncrypt(cert, Buffer.from('test')); 16 | } catch (err) { 17 | throw new Error( 18 | `The certificate "${chalk.yellow(crtFile)}" is invalid.\n${err.message}` 19 | ); 20 | } 21 | 22 | try { 23 | // privateDecrypt will throw an error with an invalid key 24 | crypto.privateDecrypt(key, encrypted); 25 | } catch (err) { 26 | throw new Error( 27 | `The certificate key "${chalk.yellow(keyFile)}" is invalid.\n${ 28 | err.message 29 | }` 30 | ); 31 | } 32 | } 33 | 34 | // Read file and throw an error if it doesn't exist 35 | function readEnvFile(file, type) { 36 | if (!fs.existsSync(file)) { 37 | throw new Error( 38 | `You specified ${chalk.cyan( 39 | type 40 | )} in your env, but the file "${chalk.yellow(file)}" can't be found.` 41 | ); 42 | } 43 | return fs.readFileSync(file); 44 | } 45 | 46 | // Get the https config 47 | // Return cert files if provided in env, otherwise just true or false 48 | function getHttpsConfig() { 49 | const {SSL_CRT_FILE, SSL_KEY_FILE, HTTPS} = process.env; 50 | const isHttps = HTTPS === 'true'; 51 | 52 | if (isHttps && SSL_CRT_FILE && SSL_KEY_FILE) { 53 | const crtFile = path.resolve(paths.appPath, SSL_CRT_FILE); 54 | const keyFile = path.resolve(paths.appPath, SSL_KEY_FILE); 55 | const config = { 56 | cert: readEnvFile(crtFile, 'SSL_CRT_FILE'), 57 | key: readEnvFile(keyFile, 'SSL_KEY_FILE'), 58 | }; 59 | 60 | validateKeyAndCerts({...config, keyFile, crtFile}); 61 | return config; 62 | } 63 | return isHttps; 64 | } 65 | 66 | module.exports = getHttpsConfig; 67 | -------------------------------------------------------------------------------- /config/jest/cssTransform.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | // This is a custom Jest transformer turning style imports into empty objects. 4 | // http://facebook.github.io/jest/docs/en/webpack.html 5 | 6 | module.exports = { 7 | process() { 8 | return 'module.exports = {};'; 9 | }, 10 | getCacheKey() { 11 | // The output is always the same. 12 | return 'cssTransform'; 13 | }, 14 | }; 15 | -------------------------------------------------------------------------------- /config/jest/fileTransform.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | const path = require('path'); 4 | const camelcase = require('camelcase'); 5 | 6 | // This is a custom Jest transformer turning file imports into filenames. 7 | // http://facebook.github.io/jest/docs/en/webpack.html 8 | 9 | module.exports = { 10 | process(src, filename) { 11 | const assetFilename = JSON.stringify(path.basename(filename)); 12 | 13 | if (filename.match(/\.svg$/)) { 14 | // Based on how SVGR generates a component name: 15 | // https://github.com/smooth-code/svgr/blob/01b194cf967347d43d4cbe6b434404731b87cf27/packages/core/src/state.js#L6 16 | const pascalCaseFilename = camelcase(path.parse(filename).name, { 17 | pascalCase: true, 18 | }); 19 | const componentName = `Svg${pascalCaseFilename}`; 20 | return `const React = require('react'); 21 | module.exports = { 22 | __esModule: true, 23 | default: ${assetFilename}, 24 | ReactComponent: React.forwardRef(function ${componentName}(props, ref) { 25 | return { 26 | $$typeof: Symbol.for('react.element'), 27 | type: 'svg', 28 | ref: ref, 29 | key: null, 30 | props: Object.assign({}, props, { 31 | children: ${assetFilename} 32 | }) 33 | }; 34 | }), 35 | };`; 36 | } 37 | 38 | return `module.exports = ${assetFilename};`; 39 | }, 40 | }; 41 | -------------------------------------------------------------------------------- /config/paths.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | const path = require('path'); 4 | const fs = require('fs'); 5 | const getPublicUrlOrPath = require('react-dev-utils/getPublicUrlOrPath'); 6 | 7 | // Make sure any symlinks in the project folder are resolved: 8 | // https://github.com/facebook/create-react-app/issues/637 9 | const appDirectory = fs.realpathSync(process.cwd()); 10 | const resolveApp = relativePath => path.resolve(appDirectory, relativePath); 11 | 12 | // We use `PUBLIC_URL` environment variable or "homepage" field to infer 13 | // "public path" at which the app is served. 14 | // webpack needs to know it to put the right