diff --git a/packages/create-react-native-library/src/index.ts b/packages/create-react-native-library/src/index.ts index 9ce6616b2..4f6f31493 100644 --- a/packages/create-react-native-library/src/index.ts +++ b/packages/create-react-native-library/src/index.ts @@ -13,6 +13,7 @@ import generateExampleApp, { } from './utils/generateExampleApp'; import { spawn } from './utils/spawn'; import { version } from '../package.json'; +import { addCodegenBuildScript } from './utils/addCodegenBuildScript'; const FALLBACK_BOB_VERSION = '0.29.0'; @@ -788,6 +789,10 @@ async function create(_argv: yargs.Arguments) { rootPackageJson.devDependencies['react-native'] = examplePackageJson.dependencies['react-native']; } + + if (arch !== 'legacy') { + addCodegenBuildScript(folder, options.project.name); + } } // Some of the passed args can already be derived from the generated package.json file. diff --git a/packages/create-react-native-library/src/utils/addCodegenBuildScript.ts b/packages/create-react-native-library/src/utils/addCodegenBuildScript.ts new file mode 100644 index 000000000..110200c76 --- /dev/null +++ b/packages/create-react-native-library/src/utils/addCodegenBuildScript.ts @@ -0,0 +1,98 @@ +import path from 'path'; +import fs from 'fs-extra'; + +// This is added to the example app's build.gradle file to invoke codegen before every build +const GRADLE_INVOKE_CODEGEN_TASK = ` +def isNewArchitectureEnabled() { + return rootProject.hasProperty("newArchEnabled") && rootProject.getProperty("newArchEnabled") == "true" +} + +if (isNewArchitectureEnabled()) { + // Since our library doesn't invoke codegen automatically we need to do it here. + tasks.register('invokeLibraryCodegen', Exec) { + workingDir "$rootDir/../../" + commandLine "npx", "bob", "build", "--target", "codegen" + } + preBuild.dependsOn invokeLibraryCodegen +}`; + +// This is added to the example app's xcscheme file to invoke codegen before every build +const XCODE_INVOKE_CODEGEN_ACTION = ` + + + + + + `; + +// You need to have the files before calling pod install otherwise they won't be registered in your pod. +// So we add a pre_install hook to the podfile that invokes codegen +const PODSPEC_INVOKE_CODEGEN_SCRIPT = ` + pre_install do |installer| + system("cd ../../ && npx bob build --target codegen") + end +`; + +/** + * Codegen isn't invoked for libraries with `includesGeneratedCode` set to `true`. + * This patches the example app to invoke library codegen on every app build. + */ +export async function addCodegenBuildScript( + libraryPath: string, + projectName: string +) { + const appBuildGradlePath = path.join( + libraryPath, + 'example', + 'android', + 'app', + 'build.gradle' + ); + const exampleAppBuildSchemePath = path.join( + libraryPath, + 'example', + 'ios', + `${projectName}Example.xcodeproj`, + 'xcshareddata', + 'xcschemes', + `${projectName}Example.xcscheme` + ); + const podfilePath = path.join(libraryPath, 'example', 'ios', 'Podfile'); + + // Add a gradle task that runs before every build + let appBuildGradle = (await fs.readFile(appBuildGradlePath)).toString(); + appBuildGradle += GRADLE_INVOKE_CODEGEN_TASK; + + await fs.writeFile(appBuildGradlePath, appBuildGradle); + + // Add an XCode prebuild action. + const exampleAppBuildScheme = (await fs.readFile(exampleAppBuildSchemePath)) + .toString() + .split('\n'); + // Used XCode and inspected the result to determine where it inserts the actions + const actionTargetLineIndex = exampleAppBuildScheme.findIndex((line) => + line.includes('') + ); + exampleAppBuildScheme.splice( + actionTargetLineIndex, + 0, + XCODE_INVOKE_CODEGEN_ACTION + ); + + await fs.writeFile( + exampleAppBuildSchemePath, + exampleAppBuildScheme.join('\n') + ); + + // Add a preinstall action to the podfile that invokes codegen + const podfile = (await fs.readFile(podfilePath)).toString().split('\n'); + const podfilePostInstallIndex = podfile.findIndex((line) => + line.includes('post_install do |installer|') + ); + podfile.splice(podfilePostInstallIndex, 0, PODSPEC_INVOKE_CODEGEN_SCRIPT); + + await fs.writeFile(podfilePath, podfile.join('\n')); +} diff --git a/packages/create-react-native-library/templates/common/$.gitignore b/packages/create-react-native-library/templates/common/$.gitignore index d3b53dfce..cfe2bb4fd 100644 --- a/packages/create-react-native-library/templates/common/$.gitignore +++ b/packages/create-react-native-library/templates/common/$.gitignore @@ -76,3 +76,7 @@ android/keystores/debug.keystore # generated by bob lib/ + +# React Native Codegen +ios/generated +android/generated diff --git a/packages/create-react-native-library/templates/common/$package.json b/packages/create-react-native-library/templates/common/$package.json index 4dea8f8b9..c6dc50826 100644 --- a/packages/create-react-native-library/templates/common/$package.json +++ b/packages/create-react-native-library/templates/common/$package.json @@ -168,6 +168,9 @@ "source": "src", "output": "lib", "targets": [ +<% if (project.arch !== 'legacy') { -%> + "codegen", +<% } -%> [ "commonjs", { @@ -191,8 +194,16 @@ }, "codegenConfig": { "name": "RN<%- project.name -%><%- project.view ? 'View': '' -%>Spec", - "type": <%- project.view ? '"components"': '"modules"' %>, - "jsSrcsDir": "src" + "type": "all", + "jsSrcsDir": "src", + "outputDir": { + "ios": "ios/generated", + "android": "android/generated" + }, + "android": { + "javaPackageName": "com.<%- project.package %>" + }, + "includesGeneratedCode": true <% } -%> } } diff --git a/packages/create-react-native-library/templates/native-common/android/build.gradle b/packages/create-react-native-library/templates/native-common/android/build.gradle index fb68eaefe..d9ad185bd 100644 --- a/packages/create-react-native-library/templates/native-common/android/build.gradle +++ b/packages/create-react-native-library/templates/native-common/android/build.gradle @@ -114,8 +114,8 @@ android { main { if (isNewArchitectureEnabled()) { java.srcDirs += [ - // This is needed to build Kotlin project with NewArch enabled - "${project.buildDir}/generated/source/codegen/java" + "generated/java", + "generated/jni" ] } } @@ -127,8 +127,9 @@ android { if (isNewArchitectureEnabled()) { java.srcDirs += [ "src/newarch", - // This is needed to build Kotlin project with NewArch enabled - "${project.buildDir}/generated/source/codegen/java" + // Codegen specs + "generated/java", + "generated/jni" ] } else { java.srcDirs += ["src/oldarch"] diff --git a/packages/create-react-native-library/templates/native-common/{%- project.identifier %}.podspec b/packages/create-react-native-library/templates/native-common/{%- project.identifier %}.podspec index 0b14c03b2..9bacfc2fd 100644 --- a/packages/create-react-native-library/templates/native-common/{%- project.identifier %}.podspec +++ b/packages/create-react-native-library/templates/native-common/{%- project.identifier %}.podspec @@ -18,6 +18,8 @@ Pod::Spec.new do |s| s.source_files = "ios/**/*.{h,m,mm}", "cpp/**/*.{hpp,cpp,c,h}" <% } else if (project.swift) { -%> s.source_files = "ios/**/*.{h,m,mm,swift}" +<% } else if (project.arch !== "legacy") { -%> + s.source_files = "ios/**/*.{h,m,mm,cpp}" <% } else { -%> s.source_files = "ios/**/*.{h,m,mm}" <% } -%> diff --git a/packages/create-react-native-library/templates/native-library-mixed/react-native.config.js b/packages/create-react-native-library/templates/native-library-mixed/react-native.config.js new file mode 100644 index 000000000..21980d40d --- /dev/null +++ b/packages/create-react-native-library/templates/native-library-mixed/react-native.config.js @@ -0,0 +1,12 @@ +/** + * @type {import('@react-native-community/cli-types').UserDependencyConfig} + */ +module.exports = { + dependency: { + platforms: { + android: { + cmakeListsPath: 'generated/jni/CMakeLists.txt', + }, + }, + }, +}; diff --git a/packages/create-react-native-library/templates/native-library-new/react-native.config.js b/packages/create-react-native-library/templates/native-library-new/react-native.config.js new file mode 100644 index 000000000..21980d40d --- /dev/null +++ b/packages/create-react-native-library/templates/native-library-new/react-native.config.js @@ -0,0 +1,12 @@ +/** + * @type {import('@react-native-community/cli-types').UserDependencyConfig} + */ +module.exports = { + dependency: { + platforms: { + android: { + cmakeListsPath: 'generated/jni/CMakeLists.txt', + }, + }, + }, +}; diff --git a/packages/create-react-native-library/templates/native-view-mixed/react-native.config.js b/packages/create-react-native-library/templates/native-view-mixed/react-native.config.js new file mode 100644 index 000000000..21980d40d --- /dev/null +++ b/packages/create-react-native-library/templates/native-view-mixed/react-native.config.js @@ -0,0 +1,12 @@ +/** + * @type {import('@react-native-community/cli-types').UserDependencyConfig} + */ +module.exports = { + dependency: { + platforms: { + android: { + cmakeListsPath: 'generated/jni/CMakeLists.txt', + }, + }, + }, +}; diff --git a/packages/create-react-native-library/templates/native-view-new/react-native.config.js b/packages/create-react-native-library/templates/native-view-new/react-native.config.js new file mode 100644 index 000000000..21980d40d --- /dev/null +++ b/packages/create-react-native-library/templates/native-view-new/react-native.config.js @@ -0,0 +1,12 @@ +/** + * @type {import('@react-native-community/cli-types').UserDependencyConfig} + */ +module.exports = { + dependency: { + platforms: { + android: { + cmakeListsPath: 'generated/jni/CMakeLists.txt', + }, + }, + }, +}; diff --git a/packages/react-native-builder-bob/package.json b/packages/react-native-builder-bob/package.json index f7a3c446b..a2d55fe7a 100644 --- a/packages/react-native-builder-bob/package.json +++ b/packages/react-native-builder-bob/package.json @@ -79,10 +79,12 @@ "@types/fs-extra": "^9.0.13", "@types/glob": "^7.2.0", "@types/json5": "^2.2.0", + "@types/mock-fs": "^4.13.4", "@types/prompts": "^2.0.14", "@types/which": "^2.0.1", "@types/yargs": "^17.0.10", "concurrently": "^7.2.2", - "jest": "^29.7.0" + "jest": "^29.7.0", + "mock-fs": "^5.2.0" } } diff --git a/packages/react-native-builder-bob/src/__tests__/patchCodegen.test.ts b/packages/react-native-builder-bob/src/__tests__/patchCodegen.test.ts new file mode 100644 index 000000000..90d971c6c --- /dev/null +++ b/packages/react-native-builder-bob/src/__tests__/patchCodegen.test.ts @@ -0,0 +1,94 @@ +import { expect, it, describe, beforeEach, afterEach } from '@jest/globals'; +import fs from 'fs-extra'; +import path from 'node:path'; +import { patchCodegen } from '../utils/patchCodegen'; +import mockfs from 'mock-fs'; +import type { Report } from '../types'; + +const mockPackageJson = { + codegenConfig: { + outputDir: { + android: 'android/generated', + }, + android: { + javaPackageName: 'com.bobtest', + }, + }, +}; + +const mockReport: Report = { + info: console.log, + warn: console.log, + error: console.log, + success: console.log, +}; + +const mockJavaSpec = ` +/** + * Some comment + */ + +package com.bobtest; + +import com.example.exampleimport; + +class SomeClass { + public void someMethod() { + // some code + } +}`; + +const mockProjectPath = path.resolve(__dirname, 'mockProject'); +const mockCodegenSpecsPath = path.resolve( + mockProjectPath, + 'android/generated/java/com/facebook/fbreact/specs' +); + +describe('patchCodegen', () => { + beforeEach(() => { + mockfs({ + [mockProjectPath]: { + 'package.json': JSON.stringify(mockPackageJson), + }, + [mockCodegenSpecsPath]: { + 'NativeBobtestSpec.java': mockJavaSpec, + }, + }); + }); + + afterEach(() => { + mockfs.restore(); + }); + + it('moves the files to correct dir', async () => { + await patchCodegen(mockProjectPath, mockPackageJson, mockReport); + + const expectedDir = path.resolve( + mockProjectPath, + 'android/generated/java/com/bobtest' + ); + + expect(await fs.pathExists(expectedDir)).toBe(true); + }); + + it('replaces the package name in the files', async () => { + await patchCodegen(mockProjectPath, mockPackageJson, mockReport); + + const expectedDir = path.resolve( + mockProjectPath, + 'android/generated/java/com/bobtest' + ); + + const expectedFile = path.resolve(expectedDir, 'NativeBobtestSpec.java'); + + const fileContent = await fs.readFile(expectedFile, 'utf8'); + + expect(fileContent).toContain('package com.bobtest;'); + }); + + it('removes the old package dir', async () => { + await patchCodegen(mockProjectPath, mockPackageJson, mockReport); + + expect(await fs.pathExists(mockCodegenSpecsPath)).toBe(false); + }); +}); diff --git a/packages/react-native-builder-bob/src/index.ts b/packages/react-native-builder-bob/src/index.ts index 26bc8c96c..57d2e8211 100644 --- a/packages/react-native-builder-bob/src/index.ts +++ b/packages/react-native-builder-bob/src/index.ts @@ -10,7 +10,18 @@ import * as logger from './utils/logger'; import buildCommonJS from './targets/commonjs'; import buildModule from './targets/module'; import buildTypescript from './targets/typescript'; -import type { Options } from './types'; +import buildCodegen from './targets/codegen'; +import type { Options, Report, Target } from './types'; + +type ArgName = 'target'; + +const args = { + target: { + type: 'string', + description: 'The target to build', + choices: ['commonjs', 'module', 'typescript', 'codegen'] satisfies Target[], + }, +} satisfies Record; // eslint-disable-next-line import/no-commonjs, @typescript-eslint/no-var-requires const { name, version } = require('../package.json'); @@ -26,6 +37,8 @@ const explorer = cosmiconfig(name, { ], }); +const projectPackagePath = path.resolve(root, 'package.json'); + const FLOW_PRGAMA_REGEX = /\*?\s*@(flow)\b/m; yargs @@ -43,15 +56,13 @@ yargs } } - const pak = path.join(root, 'package.json'); - - if (!(await fs.pathExists(pak))) { + if (!(await fs.pathExists(projectPackagePath))) { logger.exit( `Couldn't find a 'package.json' file in '${root}'.\n Are you in a project folder?` ); } - const pkg = JSON.parse(await fs.readFile(pak, 'utf-8')); + const pkg = JSON.parse(await fs.readFile(projectPackagePath, 'utf-8')); const result = await explorer.search(); if (result?.config && pkg.devDependencies && name in pkg.devDependencies) { @@ -396,7 +407,7 @@ yargs pkg.eslintIgnore.push(`${output}/`); } - await fs.writeJSON(pak, pkg, { + await fs.writeJSON(projectPackagePath, pkg, { spaces: 2, }); @@ -436,7 +447,13 @@ yargs `) ); }) - .command('build', 'build files for publishing', {}, async (argv) => { + .command('build', 'build files for publishing', args, async (argv) => { + if (!(await fs.pathExists(projectPackagePath))) { + throw new Error( + `Couldn't find a 'package.json' file in '${root}'. Are you in a project folder?` + ); + } + const result = await explorer.search(); if (!result?.config) { @@ -488,47 +505,81 @@ yargs success: logger.success, }; - for (const target of options.targets!) { - const targetName = Array.isArray(target) ? target[0] : target; - const targetOptions = Array.isArray(target) ? target[1] : undefined; - - report.info(`Building target ${kleur.blue(targetName)}`); - - switch (targetName) { - case 'commonjs': - await buildCommonJS({ - root, - source: path.resolve(root, source as string), - output: path.resolve(root, output as string, 'commonjs'), - exclude, - options: targetOptions, - report, - }); - break; - case 'module': - await buildModule({ - root, - source: path.resolve(root, source as string), - output: path.resolve(root, output as string, 'module'), - exclude, - options: targetOptions, - report, - }); - break; - case 'typescript': - await buildTypescript({ - root, - source: path.resolve(root, source as string), - output: path.resolve(root, output as string, 'typescript'), - options: targetOptions, - report, - }); - break; - default: - logger.exit(`Invalid target ${kleur.blue(targetName)}.`); + if (argv.target != null) { + buildTarget( + argv.target, + report, + source as string, + output as string, + exclude + ); + } else { + for (const target of options.targets!) { + buildTarget( + target, + report, + source as string, + output as string, + exclude + ); } } }) .demandCommand() .recommendCommands() .strict().argv; + +async function buildTarget( + target: Exclude[number], + report: Report, + source: string, + output: string, + exclude: string +) { + const targetName = Array.isArray(target) ? target[0] : target; + const targetOptions = Array.isArray(target) ? target[1] : undefined; + + report.info(`Building target ${kleur.blue(targetName)}`); + + switch (targetName) { + case 'commonjs': + await buildCommonJS({ + root, + source: path.resolve(root, source), + output: path.resolve(root, output, 'commonjs'), + exclude, + options: targetOptions, + report, + }); + break; + case 'module': + await buildModule({ + root, + source: path.resolve(root, source), + output: path.resolve(root, output, 'module'), + exclude, + options: targetOptions, + report, + }); + break; + case 'typescript': + await buildTypescript({ + root, + source: path.resolve(root, source), + output: path.resolve(root, output, 'typescript'), + options: targetOptions, + report, + }); + break; + case 'codegen': + await buildCodegen({ + root, + source: path.resolve(root, source), + output: path.resolve(root, output, 'typescript'), + report, + }); + break; + default: + logger.exit(`Invalid target ${kleur.blue(targetName)}.`); + } +} diff --git a/packages/react-native-builder-bob/src/targets/codegen.ts b/packages/react-native-builder-bob/src/targets/codegen.ts new file mode 100644 index 000000000..cff50a9e3 --- /dev/null +++ b/packages/react-native-builder-bob/src/targets/codegen.ts @@ -0,0 +1,60 @@ +import kleur from 'kleur'; +import type { Input } from '../types'; +import { patchCodegen } from '../utils/patchCodegen'; +import { spawn } from '../utils/spawn'; +import fs from 'fs-extra'; +import path from 'path'; +import del from 'del'; + +type Options = Input; + +export default async function build({ root, report }: Options) { + const packageJsonPath = path.resolve(root, 'package.json'); + const packageJson = await fs.readJson(packageJsonPath); + + const codegenIosPath = packageJson.codegenConfig?.outputDir?.ios; + if (codegenIosPath != null) { + report.info( + `Cleaning up previous iOS codegen native code at ${kleur.blue( + path.relative(root, codegenIosPath) + )}` + ); + await del([codegenIosPath]); + } + + const codegenAndroidPath = packageJson.codegenConfig?.outputDir?.android; + if (codegenAndroidPath != null) { + report.info( + `Cleaning up previous Android codegen native code at ${kleur.blue( + path.relative(root, codegenAndroidPath) + )}` + ); + await del([codegenAndroidPath]); + } + + try { + await spawn('npx', ['react-native', 'codegen'], { + stdio: 'ignore', + }); + + patchCodegen(root, packageJson, report); + + report.success('Generated native code with codegen'); + } catch (e: unknown) { + if (e != null && typeof e === 'object') { + if ('stdout' in e && e.stdout != null) { + report.error( + `Errors found while generating codegen files:\n${e.stdout.toString()}` + ); + } else if ('message' in e && typeof e.message === 'string') { + report.error(e.message); + } else { + throw e; + } + } else { + throw e; + } + + throw new Error('Failed generate the codegen files.'); + } +} diff --git a/packages/react-native-builder-bob/src/types.ts b/packages/react-native-builder-bob/src/types.ts index d893a9e22..39848d71e 100644 --- a/packages/react-native-builder-bob/src/types.ts +++ b/packages/react-native-builder-bob/src/types.ts @@ -13,11 +13,11 @@ export type Input = { report: Report; }; -export type Target = 'commonjs' | 'module' | 'typescript'; +export type Target = 'commonjs' | 'module' | 'typescript' | 'codegen'; export type Options = { source?: string; output?: string; - targets?: (Target | [Target, object])[]; + targets?: (Target | [target: Target, options: object])[]; exclude?: string; }; diff --git a/packages/react-native-builder-bob/src/utils/patchCodegen.ts b/packages/react-native-builder-bob/src/utils/patchCodegen.ts new file mode 100644 index 000000000..64aab82dd --- /dev/null +++ b/packages/react-native-builder-bob/src/utils/patchCodegen.ts @@ -0,0 +1,95 @@ +import fs from 'fs-extra'; +import path from 'path'; +import type { Report } from '../types'; + +const CODEGEN_DOCS = + 'https://github.com/reactwg/react-native-new-architecture/blob/main/docs/enable-libraries-prerequisites.md#configure-codegen'; + +/** + * Currently, running react-native codegen generates java files with package name `com.facebook.fbreact.specs`. + * This is a known issue in react-native itself. + * You can find the relevant line here: https://github.com/facebook/react-native/blob/dc460147bb00d6f912cc0a829f8040d85faeeb13/packages/react-native/scripts/codegen/generate-artifacts-executor.js#L459. + * To workaround, this function renames the package name to the one provided in the codegenConfig. + * @throws if codegenConfig.outputDir.android or codegenConfig.android.javaPackageName is not defined in package.json + * @throws if the codegenAndroidPath does not exist + */ +export async function patchCodegen( + projectPath: string, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + packageJson: any, + report: Report +) { + let codegenAndroidPath: string | undefined = + packageJson.codegenConfig.outputDir.android; + if (!codegenAndroidPath) { + throw new Error( + `Your package.json doesn't contain codegenConfig.outputDir.android. Please see ${CODEGEN_DOCS}` + ); + } + codegenAndroidPath = path.resolve(projectPath, codegenAndroidPath); + + if (!(await fs.pathExists(codegenAndroidPath))) { + throw new Error( + `The codegen android path defined in your package.json: ${codegenAndroidPath} doesnt' exist.` + ); + } + + const codegenJavaPackageName: string | undefined = + packageJson.codegenConfig.android.javaPackageName; + if (!codegenJavaPackageName) { + throw new Error( + `Your package.json doesn't contain codegenConfig.android.javaPackageName. Please see ${CODEGEN_DOCS}` + ); + } + + const codegenJavaPath = path.resolve( + codegenAndroidPath, + `java/com/facebook/fbreact/specs` + ); + + // If this issue is ever fixed in react-native, this check will prevent the patching from running. + if (!(await fs.pathExists(codegenJavaPath))) { + report.info( + `Could not find ${codegenJavaPath}. Skipping patching codegen java files.` + ); + return; + } + + const javaFiles = await fs.readdir(codegenJavaPath); + + await Promise.all( + javaFiles.map(async (file) => { + const filePath = path.resolve(codegenJavaPath, file); + const fileContent = await fs.readFile(filePath, 'utf8'); + + const newFileContent = fileContent.replace( + 'package com.facebook.fbreact.specs', + `package ${codegenJavaPackageName}` + ); + + await fs.writeFile(filePath, newFileContent); + }) + ); + + const newPackagePath = path.resolve( + codegenAndroidPath, + `java/${codegenJavaPackageName.replace(/\./g, '/')}` + ); + + if (!(await fs.pathExists(newPackagePath))) { + await fs.mkdir(newPackagePath, { recursive: true }); + } + + await Promise.all( + javaFiles.map(async (file) => { + const filePath = path.resolve(codegenJavaPath, file); + const newFilePath = path.resolve(newPackagePath, file); + + await fs.rename(filePath, newFilePath); + }) + ); + + await fs.rm(path.resolve(codegenAndroidPath, 'java/com/facebook'), { + recursive: true, + }); +} diff --git a/packages/react-native-builder-bob/src/utils/spawn.ts b/packages/react-native-builder-bob/src/utils/spawn.ts new file mode 100644 index 000000000..775fe7de1 --- /dev/null +++ b/packages/react-native-builder-bob/src/utils/spawn.ts @@ -0,0 +1,29 @@ +import crossSpawn from 'cross-spawn'; + +export const spawn = async (...args: Parameters) => { + return new Promise((resolve, reject) => { + const child = crossSpawn(...args); + + let stdout = ''; + let stderr = ''; + + child.stdout?.setEncoding('utf8'); + child.stdout?.on('data', (data) => { + stdout += data; + }); + + child.stderr?.setEncoding('utf8'); + child.stderr?.on('data', (data) => { + stderr += data; + }); + + child.once('error', reject); + child.once('close', (code) => { + if (code === 0) { + resolve(stdout.trim()); + } else { + reject(new Error(stderr.trim())); + } + }); + }); +}; diff --git a/yarn.lock b/yarn.lock index f586f877e..25bc214ee 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3531,6 +3531,15 @@ __metadata: languageName: node linkType: hard +"@types/mock-fs@npm:^4.13.4": + version: 4.13.4 + resolution: "@types/mock-fs@npm:4.13.4" + dependencies: + "@types/node": "*" + checksum: 9f886d67186da2e5cdabc32835c49ede9749146768e22c7f0f2548587e5201235d3726adef917fa6efbb9b73ad51278858a5d34e7cdc0a4b413c8396b37c350d + languageName: node + linkType: hard + "@types/ms@npm:*": version: 0.7.32 resolution: "@types/ms@npm:0.7.32" @@ -11619,6 +11628,13 @@ __metadata: languageName: node linkType: hard +"mock-fs@npm:^5.2.0": + version: 5.2.0 + resolution: "mock-fs@npm:5.2.0" + checksum: c25835247bd26fa4e0189addd61f98973f61a72741e4d2a5694b143a2069b84978443a7ac0fdb1a71aead99273ec22ff4e9c968de11bbd076db020264c5b8312 + languageName: node + linkType: hard + "modify-values@npm:^1.0.0": version: 1.0.1 resolution: "modify-values@npm:1.0.1" @@ -13000,6 +13016,7 @@ __metadata: "@types/fs-extra": ^9.0.13 "@types/glob": ^7.2.0 "@types/json5": ^2.2.0 + "@types/mock-fs": ^4.13.4 "@types/prompts": ^2.0.14 "@types/which": ^2.0.1 "@types/yargs": ^17.0.10 @@ -13018,6 +13035,7 @@ __metadata: json5: ^2.2.1 kleur: ^4.1.4 metro-config: ^0.80.9 + mock-fs: ^5.2.0 prompts: ^2.4.2 which: ^2.0.2 yargs: ^17.5.1