From 7c1dcd562483aa36d2219d0ebf81d61668dba9e2 Mon Sep 17 00:00:00 2001 From: Joey Cozza Date: Thu, 27 Aug 2026 10:47:24 -0600 Subject: [PATCH 1/4] Migrate CI from Travis to GitHub Actions Replaces .travis.yml with .github/workflows/ci.yml, preserving all three things the Travis pipeline did: publish a CI prerelease of @fs/react-scripts on every run, smoke-test that prerelease by scaffolding a real app against it, and publish the committed version on frontierMaster / v7.x / next. Artifactory auth now comes from actions/setup-node's registry-url instead of three hand-written .npmrc files. `scope` is deliberately omitted so setup-node writes a bare `registry=` line and all traffic keeps resolving through the jfrog virtual registry, preserving curation enforcement. CI prereleases are relabeled X.Y.Z-prerelease.. GITHUB_RUN_NUMBER restarts at 1, but semver compares alphanumeric prerelease identifiers in ASCII order and 'T' (84) < 'p' (112), so 8.17.0-prerelease.1 still sorts above 8.17.0-TravisPrerelease.2311 and the `next` dist-tag does not move backwards. Bumps @fs/react-scripts to 8.16.3 so the frontierInit.js change actually ships; at 8.16.2 npmPublish would no-op. Also drops upstream Facebook CI config dead since the fork: azure-pipelines*, .github/workflows/integration.yml, and the Azure badge in README. Co-Authored-By: Claude Opus 5 (1M context) --- .github/workflows/ci.yml | 77 +++++++++++++++++++ .github/workflows/integration.yml | 32 -------- .travis.yml | 67 ---------------- CHANGELOG-FRONTIER.md | 8 ++ README.md | 2 +- azure-pipelines-test-job.yml | 41 ---------- azure-pipelines.yml | 77 ------------------- freshCraTemplateUpdate.sh | 2 +- packages/react-scripts/package.json | 2 +- .../scripts/utils/frontierInit.js | 22 +++--- publishPrReleaseAndCreateFreshCraTemplate.js | 4 +- 11 files changed, 103 insertions(+), 231 deletions(-) create mode 100644 .github/workflows/ci.yml delete mode 100644 .github/workflows/integration.yml delete mode 100644 .travis.yml delete mode 100644 azure-pipelines-test-job.yml delete mode 100644 azure-pipelines.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000000..324e8c3251 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,77 @@ +# Build, publish, and smoke-test @fs/react-scripts. Replaces .travis.yml. +# +# Every run publishes a CI prerelease of @fs/react-scripts to Artifactory, then +# scaffolds a real app against that exact prerelease and builds/tests it. That +# scaffolded app is the only real test in this pipeline -- the root `npm test` +# and `npm run test:integration` scripts are both stubbed to an echo. +# +# Pushes to the three release branches additionally publish the committed +# version of @fs/react-scripts. + +name: CI + +on: + push: + branches: [frontierMaster, v7.x, next] + pull_request: + branches-ignore: [master] + workflow_dispatch: + +concurrency: + group: ci-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + build-and-publish: + name: build-and-publish + runs-on: ubuntu-latest + timeout-minutes: 45 + steps: + - uses: actions/checkout@v4 + + # Replaces the whole .travis.yml `before_install` block, which hand-wrote + # .npmrc in three places. + # + # No `scope:` on purpose. With a scope, setup-node writes `@fs:registry=` + # and only @fs packages resolve through Artifactory; without one it writes + # a bare `registry=` line, so ALL traffic goes through the jfrog virtual + # registry as it did on Travis. That keeps jfrog curation in play -- see + # the E403 diagnostic in publishPrReleaseAndCreateFreshCraTemplate.js. + # + # setup-node exports NPM_CONFIG_USERCONFIG and NODE_AUTH_TOKEN job-wide, + # so auth survives every later step and every `cd`. + - uses: actions/setup-node@v4 + with: + node-version: '24' + registry-url: https://familysearch.jfrog.io/artifactory/api/npm/fs-npm-prod-virtual/ + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_PUBLISH_TOKEN }} + + # Plain `npm install`, matching Travis. `npm ci` would fail in + # packages/react-scripts, which has no lockfile of its own. The `cd` is + # what Travis did -- `--prefix` has different semantics inside an npm + # workspaces root, so don't "simplify" it. + - name: Install + run: | + set -euo pipefail + npm install + cd packages/react-scripts + npm install + + - name: Publish CI prerelease and scaffold a fresh app + run: node publishPrReleaseAndCreateFreshCraTemplate.js + + # The scaffolded app lives at $HOME/tmp/fresh-cra-template, hardcoded in + # the script above. `working-directory:` cannot expand $HOME, so cd here. + - name: Smoke test the scaffolded app + run: | + set -euo pipefail + cd "$HOME/tmp/fresh-cra-template" + CI=true npm test + npm run build + + # Replaces the three identical `deploy: provider: script` entries. + - name: Publish release + if: github.event_name == 'push' + working-directory: packages/react-scripts + run: npm run fs-publish diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml deleted file mode 100644 index 88699dfa27..0000000000 --- a/.github/workflows/integration.yml +++ /dev/null @@ -1,32 +0,0 @@ -name: Integration Tests - -on: - push: - branches: - - main - pull_request: - branches: - - main - -jobs: - job: - runs-on: ${{ matrix.os }} - strategy: - matrix: - os: ['ubuntu-latest', 'macos-latest', 'windows-latest'] - node: ['14', '16'] - steps: - - uses: actions/checkout@v2 - - name: Setup node - uses: actions/setup-node@v2 - with: - node-version: ${{ matrix.node }} - cache: 'npm' - - name: Install npm@8 - run: npm i -g npm@8 - - name: Install yarn - run: npm i -g yarn - - name: Install packages - run: npm ci --prefer-offline - - name: Run integration tests - run: npm run test:integration diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 1703cd42e0..0000000000 --- a/.travis.yml +++ /dev/null @@ -1,67 +0,0 @@ -dist: noble -language: node_js -node_js: - - '24' -before_install: - - echo "//familysearch.jfrog.io/artifactory/api/npm/fs-npm-prod-virtual/:_authToken=${NPM_PUBLISH_TOKEN}" >> ~/.npmrc - - echo "registry=https://familysearch.jfrog.io/artifactory/api/npm/fs-npm-prod-virtual/" >> ~/.npmrc - # because this is a weird monorepo and we cd into and out of directories, this is what worked for travis to publish - - echo "//familysearch.jfrog.io/artifactory/api/npm/fs-npm-prod-virtual/:_authToken=${NPM_PUBLISH_TOKEN}" >> packages/react-scripts/.npmrc - - echo "registry=https://familysearch.jfrog.io/artifactory/api/npm/fs-npm-prod-virtual/" >> packages/react-scripts/.npmrc -install: - - npm install - - cd packages/react-scripts - - npm install - - cd ../../ -script: - - npm test - - - node publishPrReleaseAndCreateFreshCraTemplate.js - - # ${HOME}/tmp/fresh-cra-template was created in script above - - cd ${HOME}/tmp/fresh-cra-template - - # this is necessary so we can do some additional npm installs in the freshCraTemplateUpdate.sh script - - echo "//familysearch.jfrog.io/artifactory/api/npm/fs-npm-prod-virtual/:_authToken=${NPM_PUBLISH_TOKEN}" >> ${HOME}/tmp/fresh-cra-template/.npmrc - - echo "registry=https://familysearch.jfrog.io/artifactory/api/npm/fs-npm-prod-virtual/" >> ${HOME}/tmp/fresh-cra-template/.npmrc - - - CI=true npm test - - # Building the app so freshCraTemplateUpdate.sh can publish it - - npm run build -before_deploy: 'cd ${TRAVIS_BUILD_DIR}/packages/react-scripts' -deploy: - - provider: script - edge: true - cleanup: false - dpl_version: 2.0.3.beta.4 - script: npm run fs-publish -- --allow-earlier-version - on: - branch: frontierMaster - - provider: script - edge: true - cleanup: false - dpl_version: 2.0.3.beta.4 - script: npm run fs-publish -- --allow-earlier-version - on: - branch: v7.x - - provider: script - edge: true - cleanup: false - dpl_version: 2.0.3.beta.4 - script: npm run fs-publish -- --allow-earlier-version - on: - branch: next - - provider: script - edge: true - cleanup: false - dpl_version: 2.0.3.beta.4 - script: echo "Not force pushing to fresh-cra-template repo anymore" # cd ${TRAVIS_BUILD_DIR} && ./freshCraTemplateUpdate.sh - on: - branch: frontierMaster -# This tells travis to not build when the branch is master. This is important to have on our master -# branch as well because anytime we merge upstream changes from facebook, travis builds their whole matrix of 24 different builds. -# We do NOT need or want that to occur. Please leave this branches.except here, even on the master branch -branches: - except: - - master diff --git a/CHANGELOG-FRONTIER.md b/CHANGELOG-FRONTIER.md index 282d52c3d0..17cff591df 100644 --- a/CHANGELOG-FRONTIER.md +++ b/CHANGELOG-FRONTIER.md @@ -1,3 +1,11 @@ +## 8.16.3 + +- Migrate CI from Travis to GitHub Actions (`.github/workflows/ci.yml`); delete `.travis.yml` + - CI prereleases are now labeled `X.Y.Z-prerelease.` instead of `X.Y.Z-TravisPrerelease.`. `GITHUB_RUN_NUMBER` restarts at 1, but semver compares alphanumeric prerelease identifiers in ASCII order and `'T'` (84) < `'p'` (112), so `8.17.0-prerelease.1` still sorts above `8.17.0-TravisPrerelease.2311` and the `next` dist-tag does not move backwards + - `frontierInit.js` now reads `GITHUB_REPOSITORY` / `GITHUB_RUN_NUMBER` instead of `TRAVIS_REPO_SLUG` / `TRAVIS_BUILD_NUMBER`. `getTravisPrereleaseVersion` is renamed to `getCiPrereleaseVersion`, with the old name kept as a deprecated alias for one release since this module is a public export + - Artifactory auth now comes from `actions/setup-node`'s `registry-url` rather than three hand-written `.npmrc` files. `scope` is deliberately omitted so a bare `registry=` line is written and *all* packages keep resolving through the jfrog virtual registry, preserving curation enforcement + - Also removes upstream Facebook CI config that had been dead since the fork: `azure-pipelines.yml`, `azure-pipelines-test-job.yml`, `.github/workflows/integration.yml`, and the Azure build badge in `README.md` (all keyed to a `main` branch this fork does not have) + ## 8.16.2 - Bump `resolve-url-loader` from `^4.0.0` to `^5.0.0` to drop the transitive `postcss@7.0.39`, which Artifactory's security policy now blocks (403 Forbidden), breaking `npm install` on every consuming app's build diff --git a/README.md b/README.md index 0849def9cb..1d38b9e796 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ Please read through - README-FRONTIER.md - CHANGELOG-FRONTIER.md -# Create React App [![Build Status](https://dev.azure.com/facebook/create-react-app/_apis/build/status/facebook.create-react-app?branchName=main)](https://dev.azure.com/facebook/create-react-app/_build/latest?definitionId=1&branchName=main) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-green.svg)](https://github.com/facebook/create-react-app/blob/main/CONTRIBUTING.md) +# Create React App [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-green.svg)](https://github.com/facebook/create-react-app/blob/main/CONTRIBUTING.md) Logo diff --git a/azure-pipelines-test-job.yml b/azure-pipelines-test-job.yml deleted file mode 100644 index cdf3f4dacf..0000000000 --- a/azure-pipelines-test-job.yml +++ /dev/null @@ -1,41 +0,0 @@ -# -# Azure Pipelines job for building and testing create-react-app on Linux, Windows, and macOS. -# - -parameters: - name: '' - testScript: '' - configurations: - LinuxNode14: { vmImage: 'ubuntu-latest', nodeVersion: 14.x } - LinuxNode16: { vmImage: 'ubuntu-latest', nodeVersion: 16.x } - -jobs: - - job: ${{ parameters.name }} - strategy: - matrix: - ${{ insert }}: ${{ parameters.configurations }} - pool: - vmImage: $(vmImage) - steps: - - script: | - git config --global core.autocrlf false - git config --global user.name "Create React App" - git config --global user.email "cra@email.com" - displayName: 'Initialize Git config' - - - checkout: self - path: create-react-app - - - task: NodeTool@0 - inputs: - versionSpec: $(nodeVersion) - displayName: 'Install Node.js' - - - script: npm i -g npm@8 - displayName: 'Update npm to v8' - - - script: npm ci - displayName: 'Run npm ci' - - - bash: ${{ parameters.testScript }} - displayName: 'Run tests' diff --git a/azure-pipelines.yml b/azure-pipelines.yml deleted file mode 100644 index 8c51a73a8c..0000000000 --- a/azure-pipelines.yml +++ /dev/null @@ -1,77 +0,0 @@ -# -# Azure Pipelines configuration for building and testing create-react-app on Linux, Windows, and macOS. -# - -trigger: - - main - -variables: - CI: true - # Overrides the Yarn and NPM cache directories so they are on the same drive as the source. This helps improve build performance on Windows hosted agents. - YARN_CACHE_FOLDER: $(Build.SourcesDirectory)/../yarn-cache - NPM_CONFIG_CACHE: $(Build.SourcesDirectory)/../npm-cache - # Sets TEMP to be on the same drive as the cloned source on Windows. This avoids test scripts that "cd" into a directory under TEMP from failing because this directory is on a different drive from the current directory. - VSTS_OVERWRITE_TEMP: True - CRA_INTERNAL_TEST: true - -# ****************************************************************************** -# Simple test suite -# ****************************************************************************** -jobs: - - template: azure-pipelines-test-job.yml - parameters: - name: Simple - testScript: tasks/e2e-simple.sh - - # ****************************************************************************** - # Installs test suite - # ****************************************************************************** - - template: azure-pipelines-test-job.yml - parameters: - name: Installs - testScript: tasks/e2e-installs.sh - - # ****************************************************************************** - # Kitchensink test suite - # ****************************************************************************** - - template: azure-pipelines-test-job.yml - parameters: - name: Kitchensink - testScript: tasks/e2e-kitchensink.sh - - # ****************************************************************************** - # Kitchensink Eject test suite - # ****************************************************************************** - # - template: azure-pipelines-test-job.yml - # parameters: - # name: KitchensinkEject - # testScript: tasks/e2e-kitchensink-eject.sh - - # ****************************************************************************** - # Behavior test suite - # ****************************************************************************** - # - template: azure-pipelines-test-job.yml - # parameters: - # name: Behavior - # testScript: tasks/e2e-behavior.sh - # configurations: - # LinuxNode14: { vmImage: 'ubuntu-latest', nodeVersion: 14.x } - # LinuxNode16: { vmImage: 'ubuntu-latest', nodeVersion: 16.x } - # WindowsNode14: { vmImage: 'windows-2019', nodeVersion: 14.x } - # WindowsNode16: { vmImage: 'windows-2019', nodeVersion: 16.x } - # MacNode14: { vmImage: 'macOS-10.15', nodeVersion: 14.x } - # MacNode16: { vmImage: 'macOS-10.15', nodeVersion: 16.x } - - # ****************************************************************************** - # Old Node test suite - # ****************************************************************************** - - job: OldNode - pool: - vmImage: ubuntu-latest - steps: - - task: NodeTool@0 - inputs: - versionSpec: 8.x - displayName: 'Install Node.js 8.x' - - bash: tasks/e2e-old-node.sh - displayName: 'Run tests' diff --git a/freshCraTemplateUpdate.sh b/freshCraTemplateUpdate.sh index c6ca62d0a3..853ab03e26 100755 --- a/freshCraTemplateUpdate.sh +++ b/freshCraTemplateUpdate.sh @@ -14,7 +14,7 @@ sed -i.bak 's/#//' blueprint.yml # @fs/react-scripts dep version points to the local file (cause of `npx create-react-app file:localReactScriptsPath`), # so we need to replace that with the actual version -NEW_CRA_VERSION=$(json -f ${TRAVIS_BUILD_DIR}/packages/react-scripts/package.json version) +NEW_CRA_VERSION=$(json -f ${GITHUB_WORKSPACE}/packages/react-scripts/package.json version) echo "NEW_CRA_VERSION: $NEW_CRA_VERSION" json -I -f package.json -e "this.dependencies[\"@fs/react-scripts\"]=\"$NEW_CRA_VERSION\"" diff --git a/packages/react-scripts/package.json b/packages/react-scripts/package.json index 2692c75ab4..e8c6b9c745 100644 --- a/packages/react-scripts/package.json +++ b/packages/react-scripts/package.json @@ -1,6 +1,6 @@ { "name": "@fs/react-scripts", - "version": "8.16.2", + "version": "8.16.3", "upstreamVersion": "5.0.1", "description": "Configuration and scripts for Create React App.", "repository": { diff --git a/packages/react-scripts/scripts/utils/frontierInit.js b/packages/react-scripts/scripts/utils/frontierInit.js index 33de3e8007..7be559fcd3 100644 --- a/packages/react-scripts/scripts/utils/frontierInit.js +++ b/packages/react-scripts/scripts/utils/frontierInit.js @@ -7,24 +7,28 @@ const semver = require('semver') const osUtils = require('./osUtils') -const { TRAVIS_REPO_SLUG, TRAVIS_BUILD_NUMBER } = process.env +const { GITHUB_REPOSITORY, GITHUB_RUN_NUMBER } = process.env module.exports = { setupFrontier, alterPackageJsonFile, - getTravisPrereleaseVersion, + getCiPrereleaseVersion, + // Deprecated alias kept for one release. This module is a public export of + // the published @fs/react-scripts package, so renaming it outright could + // break an external caller. Remove after 8.17. + getTravisPrereleaseVersion: getCiPrereleaseVersion, } /** - * Strip of any existing prerelease info and make a travisPrelease based on the travis job number + * Strip off any existing prerelease info and make a prerelease version based on the CI run number * */ -function getTravisPrereleaseVersion(originalVersion) { +function getCiPrereleaseVersion(originalVersion) { const major = semver.major(originalVersion) const minor = semver.minor(originalVersion) const patch = semver.patch(originalVersion) - return `${major}.${minor}.${patch}-TravisPrerelease.${TRAVIS_BUILD_NUMBER}` + return `${major}.${minor}.${patch}-prerelease.${GITHUB_RUN_NUMBER}` } function setupFrontier(appPath, appName) { @@ -32,13 +36,13 @@ function setupFrontier(appPath, appName) { const packageJson = { ...appPackage } delete packageJson.scripts.eject - if (TRAVIS_REPO_SLUG === 'fs-webdev/create-react-app') { + if (GITHUB_REPOSITORY === 'fs-webdev/create-react-app') { const reactScriptPackageJson = require(path.join(__dirname, '../../package.json')) - const travisPrereleaseVersion = getTravisPrereleaseVersion(reactScriptPackageJson.version) + const ciPrereleaseVersion = getCiPrereleaseVersion(reactScriptPackageJson.version) console.log( - `CI and TRAVIS_BUILD_DIR are set, so setting @fs/react-scripts to Travis prerelease version "${travisPrereleaseVersion}"` + `Running in this repo's own CI, so setting @fs/react-scripts to prerelease version "${ciPrereleaseVersion}"` ) - packageJson.dependencies['@fs/react-scripts'] = travisPrereleaseVersion + packageJson.dependencies['@fs/react-scripts'] = ciPrereleaseVersion } return packageJson }) diff --git a/publishPrReleaseAndCreateFreshCraTemplate.js b/publishPrReleaseAndCreateFreshCraTemplate.js index 17996b851d..dde78d06c6 100644 --- a/publishPrReleaseAndCreateFreshCraTemplate.js +++ b/publishPrReleaseAndCreateFreshCraTemplate.js @@ -5,7 +5,7 @@ const path = require('path') const reactScriptPath = path.join(__dirname, 'packages/react-scripts') -const { alterPackageJsonFile, getTravisPrereleaseVersion } = require(path.join( +const { alterPackageJsonFile, getCiPrereleaseVersion } = require(path.join( reactScriptPath, 'scripts/utils/frontierInit' )) @@ -16,7 +16,7 @@ let newVersion alterPackageJsonFile(reactScriptPath, packageJson => { originalVersion = packageJson.version - newVersion = getTravisPrereleaseVersion(packageJson.version) + newVersion = getCiPrereleaseVersion(packageJson.version) console.log(`setting @fs/react-scripts version to "${newVersion}" temporarily to get published`) packageJson.version = newVersion return packageJson From 24f05cf233bca5e82cd24a3fd0307da127855106 Mon Sep 17 00:00:00 2001 From: Joey Cozza Date: Thu, 27 Aug 2026 11:04:28 -0600 Subject: [PATCH 2/4] Skip git init/commit for the CI-scaffolded app create-react-app's init.js git-inits the app it scaffolds and then commits it. On a GitHub Actions runner there is no git identity, so the commit failed with status 128 and init.js deleted the .git directory it had just created -- a stack trace in the log for no result. The app scaffolded in CI is a throwaway smoke test that nothing commits or pushes, so skip the git work entirely there. Gated on a new isFrontierCi() helper in frontierInit.js, which also de-duplicates the GITHUB_REPOSITORY check that setupFrontier already made. Co-Authored-By: Claude Opus 5 (1M context) --- CHANGELOG-FRONTIER.md | 1 + packages/react-scripts/scripts/init.js | 10 +++++++--- packages/react-scripts/scripts/utils/frontierInit.js | 11 ++++++++++- 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/CHANGELOG-FRONTIER.md b/CHANGELOG-FRONTIER.md index 17cff591df..938fc45539 100644 --- a/CHANGELOG-FRONTIER.md +++ b/CHANGELOG-FRONTIER.md @@ -4,6 +4,7 @@ - CI prereleases are now labeled `X.Y.Z-prerelease.` instead of `X.Y.Z-TravisPrerelease.`. `GITHUB_RUN_NUMBER` restarts at 1, but semver compares alphanumeric prerelease identifiers in ASCII order and `'T'` (84) < `'p'` (112), so `8.17.0-prerelease.1` still sorts above `8.17.0-TravisPrerelease.2311` and the `next` dist-tag does not move backwards - `frontierInit.js` now reads `GITHUB_REPOSITORY` / `GITHUB_RUN_NUMBER` instead of `TRAVIS_REPO_SLUG` / `TRAVIS_BUILD_NUMBER`. `getTravisPrereleaseVersion` is renamed to `getCiPrereleaseVersion`, with the old name kept as a deprecated alias for one release since this module is a public export - Artifactory auth now comes from `actions/setup-node`'s `registry-url` rather than three hand-written `.npmrc` files. `scope` is deliberately omitted so a bare `registry=` line is written and *all* packages keep resolving through the jfrog virtual registry, preserving curation enforcement + - `init.js` no longer git-inits or git-commits the scaffolded app when running in this repo's own CI. The runner has no git identity, so the commit failed and `init.js` then deleted the `.git` directory it had just created — noise in the log for no result. Gated on the new `isFrontierCi()` helper, so behavior is unchanged for anyone actually running `create-react-app` - Also removes upstream Facebook CI config that had been dead since the fork: `azure-pipelines.yml`, `azure-pipelines-test-job.yml`, `.github/workflows/integration.yml`, and the Azure build badge in `README.md` (all keyed to a `main` branch this fork does not have) ## 8.16.2 diff --git a/packages/react-scripts/scripts/init.js b/packages/react-scripts/scripts/init.js index 190ef17402..f6c61dbdfb 100644 --- a/packages/react-scripts/scripts/init.js +++ b/packages/react-scripts/scripts/init.js @@ -22,7 +22,7 @@ const spawn = require('react-dev-utils/crossSpawn'); const { defaultBrowsers } = require('react-dev-utils/browsersHelper'); const os = require('os'); const verifyTypeScriptSetup = require('./utils/verifyTypeScriptSetup'); -const { setupFrontier } = require('./utils/frontierInit'); +const { setupFrontier, isFrontierCi } = require('./utils/frontierInit'); function isInGitRepository() { try { @@ -288,10 +288,14 @@ module.exports = function ( ); } - // Initialize git repo + // Initialize git repo. + // Skipped in this repo's own CI. The app scaffolded there is a throwaway + // smoke test, and the runner has no git identity, so tryGitCommit() below + // would fail and init.js would then delete the .git directory it had just + // created -- a lot of noise in the log for no result. let initializedGit = false; - if (tryGitInit()) { + if (!isFrontierCi() && tryGitInit()) { initializedGit = true; console.log(); console.log('Initialized a git repository.'); diff --git a/packages/react-scripts/scripts/utils/frontierInit.js b/packages/react-scripts/scripts/utils/frontierInit.js index 7be559fcd3..931dda5fe3 100644 --- a/packages/react-scripts/scripts/utils/frontierInit.js +++ b/packages/react-scripts/scripts/utils/frontierInit.js @@ -13,12 +13,21 @@ module.exports = { setupFrontier, alterPackageJsonFile, getCiPrereleaseVersion, + isFrontierCi, // Deprecated alias kept for one release. This module is a public export of // the published @fs/react-scripts package, so renaming it outright could // break an external caller. Remove after 8.17. getTravisPrereleaseVersion: getCiPrereleaseVersion, } +/** + * True only when running inside this repo's own CI, where any app we scaffold is + * a throwaway smoke test rather than a real user's new project. + */ +function isFrontierCi() { + return GITHUB_REPOSITORY === 'fs-webdev/create-react-app' +} + /** * Strip off any existing prerelease info and make a prerelease version based on the CI run number * @@ -36,7 +45,7 @@ function setupFrontier(appPath, appName) { const packageJson = { ...appPackage } delete packageJson.scripts.eject - if (GITHUB_REPOSITORY === 'fs-webdev/create-react-app') { + if (isFrontierCi()) { const reactScriptPackageJson = require(path.join(__dirname, '../../package.json')) const ciPrereleaseVersion = getCiPrereleaseVersion(reactScriptPackageJson.version) console.log( From 621d88fc74a53fc796fc7df5738ce247cf14ae56 Mon Sep 17 00:00:00 2001 From: Joey Cozza Date: Thu, 27 Aug 2026 11:08:27 -0600 Subject: [PATCH 3/4] bump node engine requirement --- packages/react-scripts/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-scripts/package.json b/packages/react-scripts/package.json index e8c6b9c745..8534658cbe 100644 --- a/packages/react-scripts/package.json +++ b/packages/react-scripts/package.json @@ -10,7 +10,7 @@ }, "license": "MIT", "engines": { - "node": ">=20.0.0" + "node": ">=24.0.0" }, "bugs": { "url": "https://github.com/fs-webdev/create-react-app/issues" From 6c6bee6fce7ff91c580e4b2469e4ffe6bf732711 Mon Sep 17 00:00:00 2001 From: Joey Cozza Date: Thu, 27 Aug 2026 14:12:15 -0600 Subject: [PATCH 4/4] Restrict GITHUB_TOKEN to contents: read This repo's default_workflow_permissions is "write", so without an explicit block every job gets a read/write GITHUB_TOKEN. Nothing in this workflow touches the GitHub API: checkout only reads, and both the prerelease and release publishes authenticate to Artifactory with NODE_AUTH_TOKEN. Leaving a write-scoped token in the environment while `npm install` runs lifecycle scripts across the full dependency tree of a public repo is avoidable exposure. Matches merge-lingoport-translations.yml, which already declares its permissions explicitly. Flagged by CodeQL on #429. Co-Authored-By: Claude Opus 5 (1M context) --- .github/workflows/ci.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 324e8c3251..d38db3c478 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -10,6 +10,14 @@ name: CI +# The repo default is a read/write GITHUB_TOKEN. Nothing here touches the +# GitHub API -- checkout only reads, and both publishes authenticate to +# Artifactory with NODE_AUTH_TOKEN -- so drop the token to read-only rather +# than leave a write-scoped credential in the environment while `npm install` +# runs lifecycle scripts from the whole dependency tree. +permissions: + contents: read + on: push: branches: [frontierMaster, v7.x, next]