diff --git a/.github/workflows/microsoft-pr.yml b/.github/workflows/microsoft-pr.yml index 2c1b4500b3cc..968a5d5d9bf3 100644 --- a/.github/workflows/microsoft-pr.yml +++ b/.github/workflows/microsoft-pr.yml @@ -132,13 +132,11 @@ jobs: permissions: {} uses: ./.github/workflows/microsoft-build-rntester.yml - # https://github.com/microsoft/react-native-macos/issues/2344 - # Disable these tests because verdaccio hangs - # test-react-native-macos-init: - # name: "Test react-native-macos init" - # permissions: {} - # if: ${{ endsWith(github.base_ref, '-stable') }} - # uses: ./.github/workflows/microsoft-test-react-native-macos-init.yml + test-react-native-macos-init: + name: "Test react-native-macos init" + permissions: {} + if: ${{ endsWith(github.base_ref, '-stable') }} + uses: ./.github/workflows/microsoft-test-react-native-macos-init.yml # https://github.com/microsoft/react-native-macos/issues/2344 # Disable these tests because verdaccio hangs @@ -158,7 +156,7 @@ jobs: - yarn-constraints - javascript-tests - build-rntester - # - test-react-native-macos-init + - test-react-native-macos-init # - react-native-test-app-integration steps: - name: All required jobs passed diff --git a/.github/workflows/microsoft-test-react-native-macos-init.yml b/.github/workflows/microsoft-test-react-native-macos-init.yml index 923ffd7a17a9..d16c97c42ffc 100644 --- a/.github/workflows/microsoft-test-react-native-macos-init.yml +++ b/.github/workflows/microsoft-test-react-native-macos-init.yml @@ -32,42 +32,32 @@ jobs: working-directory: packages/react-native-macos-init run: yarn build - - name: Start Verdaccio server + - name: Determine React Native version + id: rn-version run: | - set -euo pipefail - nohup npx --yes verdaccio --config .ado/verdaccio/config.yaml >/dev/null 2>&1 & - echo $! > $RUNNER_TEMP/verdaccio.pid - - name: Wait for Verdaccio to be ready - run: node .ado/scripts/waitForVerdaccio.mjs http://localhost:4873 - - - name: Configure npm for Verdaccio - run: .ado/scripts/verdaccio.sh init - - - name: Publish to Verdaccio - run: .ado/scripts/verdaccio.sh publish --branch origin/${{ github.base_ref }} - - - name: Export versions - run: node .ado/scripts/export-versions.mjs + RN_VERSION=$(node -e " + const pkg = require('./packages/react-native/package.json'); + console.log(pkg.peerDependencies['react-native']); + ") + echo "version=$RN_VERSION" >> "$GITHUB_OUTPUT" - name: Initialize new project run: | set -eox pipefail - npx --yes @react-native-community/cli init testcli --version $(cat .react_native_version) --skip-install + npx --yes @react-native-community/cli init testcli --version ${{ steps.rn-version.outputs.version }} working-directory: ${{ runner.temp }} - - name: Install dependencies in new project + - name: Install local react-native-macos working-directory: ${{ runner.temp }}/testcli run: | set -eox pipefail - yarn install --mode=update-lockfile - yarn install + npm install ${{ github.workspace }}/packages/react-native - name: Apply macOS template working-directory: ${{ runner.temp }}/testcli run: | set -eox pipefail - ${{ github.workspace }}/.ado/scripts/verdaccio.sh configure - node ${{ github.workspace }}/packages/react-native-macos-init/bin.js --verbose --version latest --overwrite --prerelease + node ${{ github.workspace }}/packages/react-native-macos-init/bin.js --verbose --overwrite --prerelease pod install --project-directory=macos - name: Build macOS app @@ -75,10 +65,3 @@ jobs: run: | set -eox pipefail npx react-native build-macos - - - name: Stop Verdaccio - if: always() - run: | - if [ -f "$RUNNER_TEMP/verdaccio.pid" ]; then - kill "$(cat $RUNNER_TEMP/verdaccio.pid)" || true - fi diff --git a/packages/react-native-macos-init/package.json b/packages/react-native-macos-init/package.json index 934a1a1529b4..5679cb4a46cb 100644 --- a/packages/react-native-macos-init/package.json +++ b/packages/react-native-macos-init/package.json @@ -17,8 +17,10 @@ "prepublishOnly": "npm run build" }, "bin": "./bin.js", + "engines": { + "node": ">=20.12.0" + }, "dependencies": { - "chalk": "^3", "find-up": "^4.1.0", "npm-registry-fetch": "^14.0.0", "prompts": "^2.3.0", @@ -27,7 +29,7 @@ }, "devDependencies": { "@rnx-kit/tsconfig": "^2.0.0", - "@types/chalk": "^2.2.0", + "@types/node": "^22.0.0", "@types/npm-registry-fetch": "^8.0.0", "@types/prompts": "^2.0.3", "@types/semver": "^7.1.0", diff --git a/packages/react-native-macos-init/src/cli.ts b/packages/react-native-macos-init/src/cli.ts index 42bb93692b80..6f6678cbd118 100644 --- a/packages/react-native-macos-init/src/cli.ts +++ b/packages/react-native-macos-init/src/cli.ts @@ -5,9 +5,9 @@ * @format */ -import chalk from 'chalk'; import {execSync} from 'child_process'; import * as findUp from 'find-up'; +import {styleText} from 'node:util'; import * as fs from 'fs'; import * as npmFetch from 'npm-registry-fetch'; import prompts from 'prompts'; @@ -60,7 +60,9 @@ function getNpmRegistryUrl(): string { } function getReactNativeAppName() { - console.log(`Reading ${chalk.cyan('application name')} from package.json…`); + console.log( + `Reading ${styleText('cyan', 'application name')} from package.json…`, + ); const cwd = process.cwd(); const pkgJsonPath = findUp.sync('package.json', {cwd}); if (!pkgJsonPath) { @@ -73,7 +75,9 @@ function getReactNativeAppName() { if (!name) { const appJsonPath = findUp.sync('app.json', {cwd}); if (appJsonPath) { - console.log(`Reading ${chalk.cyan('application name')} from app.json…`); + console.log( + `Reading ${styleText('cyan', 'application name')} from app.json…`, + ); name = JSON.parse(fs.readFileSync(appJsonPath, 'utf8')).name; } } @@ -84,7 +88,9 @@ function getReactNativeAppName() { } function getPackageVersion(packageName: string, exitOnError: boolean = true) { - console.log(`Reading ${chalk.cyan(packageName)} version from node_modules…`); + console.log( + `Reading ${styleText('cyan', packageName)} version from node_modules…`, + ); try { const pkgJsonPath = require.resolve(`${packageName}/package.json`, { @@ -112,8 +118,8 @@ function getReactNativeMacOSVersion() { } function errorOutOnUnsupportedVersionOfReactNative(rnVersion: string): never { - const version = chalk.cyan(rnVersion); - const supportedVersions = chalk.cyan('>=0.60'); + const version = styleText('cyan', rnVersion); + const supportedVersions = styleText('cyan', '>=0.60'); printError( `Unsupported version of ${RNPKG}: ${version}\n${MACOSPKG} supports ${RNPKG} versions ${supportedVersions}`, ); @@ -208,8 +214,8 @@ function isProjectUsingYarn(cwd: string) { * Outputs decorated version of the package for the CLI */ function printPkg(name: string, version?: string) { - return `${chalk.yellow(name)}${ - version ? `${chalk.grey('@')}${chalk.cyan(version)}` : '' + return `${styleText('yellow', name)}${ + version ? `${styleText('gray', '@')}${styleText('cyan', version)}` : '' }`; } @@ -217,7 +223,43 @@ function printPkg(name: string, version?: string) { * Prints decorated version of console.error to the CLI */ function printError(message: string, ...optionalParams: any[]) { - console.error(chalk.red(chalk.bold(message)), ...optionalParams); + console.error(styleText(['red', 'bold'], message), ...optionalParams); +} + +/** + * Checks if the resolved react-native-macos version's peer dependency on + * react-native is compatible with the installed version. Warns if not. + */ +async function validatePeerDependencies( + macosVersion: string, + installedRNVersion: string, +): Promise { + try { + const npmResponse = await npmFetch.json(`${MACOSPKG}/${macosVersion}`, { + registry: getNpmRegistryUrl(), + }); + const peerDeps = (npmResponse as any).peerDependencies; + if (peerDeps && peerDeps[RNPKG]) { + const requiredRN = peerDeps[RNPKG]; + if (!semver.satisfies(installedRNVersion, requiredRN)) { + console.warn( + styleText( + 'yellow', + `\n${styleText('bold', 'Warning:')} ${printPkg( + MACOSPKG, + macosVersion, + )} requires ${printPkg(RNPKG, requiredRN)}, ` + + `but you have ${printPkg( + RNPKG, + installedRNVersion, + )} installed.\n`, + ), + ); + } + } + } catch { + // Non-fatal — if we can't check, proceed anyway + } } (async () => { @@ -243,29 +285,38 @@ function printError(message: string, ...optionalParams: any[]) { if (!argv.version) { console.log( - `Latest matching version of ${chalk.green(MACOSPKG)} for ${printPkg( - RNPKG, - reactNativeVersion, - )} is ${printPkg(MACOSPKG, reactNativeMacOSResolvedVersion)}.`, + `Latest matching version of ${styleText( + 'green', + MACOSPKG, + )} for ${printPkg(RNPKG, reactNativeVersion)} is ${printPkg( + MACOSPKG, + reactNativeMacOSResolvedVersion, + )}.`, ); if (semver.prerelease(reactNativeMacOSResolvedVersion)) { console.warn( ` -${printPkg(MACOSPKG, reactNativeMacOSResolvedVersion)} is a ${chalk.bgYellow( +${printPkg(MACOSPKG, reactNativeMacOSResolvedVersion)} is a ${styleText( + 'bgYellow', 'pre-release', )} version. The latest supported version is ${printPkg( MACOSPKG, reactNativeMacOSLatestVersion, )}. -You can either downgrade your version of ${chalk.yellow(RNPKG)} to ${chalk.cyan( +You can either downgrade your version of ${styleText( + 'yellow', + RNPKG, + )} to ${styleText( + 'cyan', getMatchingReactNativeSemVerForReactNativeMacOSVersion( reactNativeMacOSLatestVersion, ), - )}, or continue with a ${chalk.bgYellow( + )}, or continue with a ${styleText( + 'bgYellow', 'pre-release', - )} version of ${chalk.yellow(MACOSPKG)}. + )} version of ${styleText('yellow', MACOSPKG)}. `, ); @@ -288,6 +339,11 @@ You can either downgrade your version of ${chalk.yellow(RNPKG)} to ${chalk.cyan( } } + await validatePeerDependencies( + reactNativeMacOSResolvedVersion, + reactNativeVersion, + ); + const pkgLatest = printPkg(MACOSPKG, version); if (reactNativeMacOSResolvedVersion !== reactNativeMacOSVersion) { @@ -298,15 +354,44 @@ You can either downgrade your version of ${chalk.yellow(RNPKG)} to ${chalk.cyan( ); const pkgmgr = isProjectUsingYarn(process.cwd()) - ? `yarn add${verbose ? '' : ' --silent'}` - : `npm install --save${verbose ? '' : ' --silent'}`; + ? 'yarn add' + : 'npm install --save'; const execOptions = verbose ? {stdio: 'inherit' as const} : {}; - execSync(`${pkgmgr} "${MACOSPKG}@${version}"`, execOptions); - console.log(`${pkgLatest} ${chalk.green('successfully installed!')}`); + try { + execSync(`${pkgmgr} "${MACOSPKG}@${version}"`, execOptions); + } catch (e: any) { + // When not verbose, execSync captures output in the error object + if (!verbose) { + if (e.stderr) { + console.error(e.stderr.toString()); + } + if (e.stdout) { + console.log(e.stdout.toString()); + } + } + printError( + `Failed to install ${printPkg(MACOSPKG, version)}.\n` + + `This can happen if there is a peer dependency mismatch between ` + + `${RNPKG} and ${MACOSPKG}.\n` + + `Check that your installed version of ${styleText( + 'yellow', + RNPKG, + )} is compatible ` + + `with ${printPkg(MACOSPKG, version)}.`, + ); + process.exit(EXITCODE_UNKNOWN_ERROR); + } + + console.log( + `${pkgLatest} ${styleText('green', 'successfully installed!')}`, + ); } else { console.log( - `${chalk.green('Latest version')} of ${pkgLatest} already installed.`, + `${styleText( + 'green', + 'Latest version', + )} of ${pkgLatest} already installed.`, ); } diff --git a/packages/react-native/local-cli/generate-macos.js b/packages/react-native/local-cli/generate-macos.js index 4d3a208879e3..bb4a9c869d34 100644 --- a/packages/react-native/local-cli/generate-macos.js +++ b/packages/react-native/local-cli/generate-macos.js @@ -3,7 +3,6 @@ const { copyProjectTemplateAndReplace, - installDependencies, printFinishMessage, } = require('./generator-macos'); const fs = require('fs'); @@ -21,8 +20,6 @@ function generateMacOS (projectDir, name, options) { fs.mkdirSync(projectDir); } - installDependencies(options); - copyProjectTemplateAndReplace( path.join(__dirname, 'generator-macos', 'templates'), projectDir, diff --git a/packages/react-native/local-cli/generator-common/index.js b/packages/react-native/local-cli/generator-common/index.js index a2a54581548f..1c2a77517ff7 100644 --- a/packages/react-native/local-cli/generator-common/index.js +++ b/packages/react-native/local-cli/generator-common/index.js @@ -1,8 +1,8 @@ // @ts-check // @noflow -const chalk = require('chalk'); const fs = require('fs'); +const { styleText } = require('node:util'); const path = require('path'); // Copied from `copyAndReplace` in react-native-community/cli as it's deleted in newer versions @@ -330,11 +330,11 @@ function alwaysOverwriteContentChangedCallback( contentChanged ) { if (contentChanged === 'new') { - console.log(`${chalk.bold('new')} ${relativeDestPath}`); + console.log(`${styleText('bold','new')} ${relativeDestPath}`); return 'overwrite'; } if (contentChanged === 'changed') { - console.log(`${chalk.bold('changed')} ${relativeDestPath} ${chalk.yellow('[overwriting]')}`); + console.log(`${styleText('bold','changed')} ${relativeDestPath} ${styleText('yellow','[overwriting]')}`); return 'overwrite'; } if (contentChanged === 'identical') { @@ -351,12 +351,12 @@ function upgradeFileContentChangedCallback( contentChanged ) { if (contentChanged === 'new') { - console.log(`${chalk.bold('new')} ${relativeDestPath}`); + console.log(`${styleText('bold','new')} ${relativeDestPath}`); return 'overwrite'; } if (contentChanged === 'changed') { console.log( - `${chalk.bold(relativeDestPath)} ` + + `${styleText('bold',relativeDestPath)} ` + `has changed in the new version.\nDo you want to keep your ${relativeDestPath} or replace it with the ` + 'latest version?\nIf you ever made any changes ' + 'to this file, you\'ll probably want to keep it.\n' + diff --git a/packages/react-native/local-cli/generator-macos/index.js b/packages/react-native/local-cli/generator-macos/index.js index ea0ffa464073..cd406c3b6d7b 100644 --- a/packages/react-native/local-cli/generator-macos/index.js +++ b/packages/react-native/local-cli/generator-macos/index.js @@ -7,9 +7,7 @@ const { copyAndReplaceAll, createDir, } = require('../generator-common'); -const chalk = require('chalk'); -const childProcess = require('child_process'); -const fs = require('fs'); +const { styleText } = require('node:util'); const path = require('path'); const macOSDir = 'macos'; @@ -102,34 +100,21 @@ function schemePath(basename, platform) { return path.join(schemesPath(basename), projectName(basename, platform) + '.xcscheme'); } -/** - * @param {{ verbose?: boolean }=} options - */ -function installDependencies(options) { - const cwd = process.cwd(); - - // Install dependencies using correct package manager - const isYarn = fs.existsSync(path.join(cwd, 'yarn.lock')); - - /** @type {{ stdio?: 'inherit' }} */ - const execOptions = options && options.verbose ? { stdio: 'inherit' } : {}; - childProcess.execSync(isYarn ? 'yarn' : 'npm i', execOptions); -} - /** * @param {string} newProjectName */ function printFinishMessage(newProjectName) { console.log(` - ${chalk.blue(`Run instructions for ${chalk.bold('macOS')}`)}: - • pod install --project-directory=macos - • npx react-native run-macos - • yarn start:macos + ${styleText('blue', `Run instructions for ${styleText('bold', 'macOS')}`)}: + • ${styleText('cyan', 'pod install --project-directory=macos')} + • ${styleText('cyan', 'npx react-native run-macos')} + + To start the Metro bundler separately: + • ${styleText('cyan', 'npx react-native start')} `); } module.exports = { copyProjectTemplateAndReplace, - installDependencies, printFinishMessage, }; diff --git a/yarn.lock b/yarn.lock index 6cf5af7025a1..522ad4d39c85 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4044,15 +4044,6 @@ __metadata: languageName: node linkType: hard -"@types/chalk@npm:^2.2.0": - version: 2.2.0 - resolution: "@types/chalk@npm:2.2.0" - dependencies: - chalk: "npm:*" - checksum: 10c0/d408e65041f37db0211574d72300058538beac430bd636e6ebc51fad15e954de35796dea42ab9706b0f1f96c15d2e68dce14de22d7a0755270a6d86ac2813390 - languageName: node - linkType: hard - "@types/chokidar@npm:^2.1.3": version: 2.1.3 resolution: "@types/chokidar@npm:2.1.3" @@ -4190,6 +4181,15 @@ __metadata: languageName: node linkType: hard +"@types/node@npm:^22.0.0": + version: 22.19.15 + resolution: "@types/node@npm:22.19.15" + dependencies: + undici-types: "npm:~6.21.0" + checksum: 10c0/f17eaf3d0d1da5e93ad9e287efb78201f8a5282973c004c5f70d91675c5c6b926a23acaa7b158a42b3d7e27e36b349d65a531710c91c308fca53dd7fa280ef98 + languageName: node + linkType: hard + "@types/npm-package-arg@npm:*": version: 6.1.2 resolution: "@types/npm-package-arg@npm:6.1.2" @@ -5751,13 +5751,6 @@ __metadata: languageName: node linkType: hard -"chalk@npm:*": - version: 5.3.0 - resolution: "chalk@npm:5.3.0" - checksum: 10c0/8297d436b2c0f95801103ff2ef67268d362021b8210daf8ddbe349695333eb3610a71122172ff3b0272f1ef2cf7cc2c41fdaa4715f52e49ffe04c56340feed09 - languageName: node - linkType: hard - "chalk@npm:^2.0.1, chalk@npm:^2.3.0": version: 2.4.2 resolution: "chalk@npm:2.4.2" @@ -5769,16 +5762,6 @@ __metadata: languageName: node linkType: hard -"chalk@npm:^3": - version: 3.0.0 - resolution: "chalk@npm:3.0.0" - dependencies: - ansi-styles: "npm:^4.1.0" - supports-color: "npm:^7.1.0" - checksum: 10c0/ee650b0a065b3d7a6fda258e75d3a86fc8e4effa55871da730a9e42ccb035bf5fd203525e5a1ef45ec2582ecc4f65b47eb11357c526b84dd29a14fb162c414d2 - languageName: node - linkType: hard - "chalk@npm:^4.0.0, chalk@npm:^4.0.2, chalk@npm:^4.1.0, chalk@npm:^4.1.2": version: 4.1.2 resolution: "chalk@npm:4.1.2" @@ -12675,13 +12658,12 @@ __metadata: resolution: "react-native-macos-init@workspace:packages/react-native-macos-init" dependencies: "@rnx-kit/tsconfig": "npm:^2.0.0" - "@types/chalk": "npm:^2.2.0" + "@types/node": "npm:^22.0.0" "@types/npm-registry-fetch": "npm:^8.0.0" "@types/prompts": "npm:^2.0.3" "@types/semver": "npm:^7.1.0" "@types/valid-url": "npm:^1.0.2" "@types/yargs": "npm:^15.0.3" - chalk: "npm:^3" find-up: "npm:^4.1.0" just-scripts: "npm:^2.3.2" npm-registry-fetch: "npm:^14.0.0"