From 5b42f440fc609d9a49b94a5435276acda9d0ade7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Burak=20G=C3=BCner?= Date: Tue, 28 Jan 2025 18:47:41 +0300 Subject: [PATCH] feat(bob): support custom target definitions (#732) ### Summary This adds the `custom` target to bob. Users are able to pass arbitrary scripts via this target and bob will call those scripts. ### Test plan 1. Configure bob in a new project 2. Define the `custom` target 3. Define a script that generates some files 4. Call bob build and make sure the script was called with the right package manager --------- Co-authored-by: Satyajit Sahoo --- docs/pages/build.md | 12 ++++ .../react-native-builder-bob/src/index.ts | 9 +++ .../src/targets/custom.ts | 72 +++++++++++++++++++ .../react-native-builder-bob/src/types.ts | 7 +- 4 files changed, 99 insertions(+), 1 deletion(-) create mode 100644 packages/react-native-builder-bob/src/targets/custom.ts diff --git a/docs/pages/build.md b/docs/pages/build.md index e3245b2ba..828eb7506 100644 --- a/docs/pages/build.md +++ b/docs/pages/build.md @@ -188,6 +188,18 @@ Generates the [React Native Codegen](https://reactnative.dev/docs/the-new-archit You can ensure your Codegen generated scaffold code is stable through different React Native versions by shipping it with your library. You can find more in the [React Native Official Docs](https://reactnative.dev/docs/the-new-architecture/codegen-cli#including-generated-code-into-libraries). +#### `custom` + +Define a custom build target. This is useful to call code generators during the build process. + +##### `script` + +Accepts a script name. `bob` will call the matching script defined under `package.json`'s `scripts` property. The build process **will throw and exit** if the target is defined without this option. + +##### `clean` + +You can pass a path to this option and `bob` will delete all the files on that path. The path is resolved relatively to where `build` was called from. + #### `commonjs` Enable compiling source files with Babel and use CommonJS module system. diff --git a/packages/react-native-builder-bob/src/index.ts b/packages/react-native-builder-bob/src/index.ts index ca696bdb7..1f1a26a8b 100644 --- a/packages/react-native-builder-bob/src/index.ts +++ b/packages/react-native-builder-bob/src/index.ts @@ -11,6 +11,7 @@ import buildCommonJS from './targets/commonjs'; import buildModule from './targets/module'; import buildTypescript from './targets/typescript'; import buildCodegen from './targets/codegen'; +import customTarget from './targets/custom'; import type { Options, Report, Target } from './types'; type ArgName = 'target'; @@ -584,6 +585,14 @@ async function buildTarget( report, }); break; + case 'custom': + await customTarget({ + options: targetOptions, + source: path.resolve(root, source), + report, + root, + }); + break; default: logger.exit(`Invalid target ${kleur.blue(targetName)}.`); } diff --git a/packages/react-native-builder-bob/src/targets/custom.ts b/packages/react-native-builder-bob/src/targets/custom.ts new file mode 100644 index 000000000..28679260f --- /dev/null +++ b/packages/react-native-builder-bob/src/targets/custom.ts @@ -0,0 +1,72 @@ +import kleur from 'kleur'; +import path from 'path'; +import fs from 'fs-extra'; +import type { Input } from '../types'; +import { spawn } from '../utils/spawn'; +import dedent from 'dedent'; +import del from 'del'; + +type Options = Omit & { + options?: { + script?: string; + clean?: string; + }; +}; + +export default async function customTarget({ options, root, report }: Options) { + if (options?.script == null) { + report.error( + dedent( + `No script was provided with the custom target. + Example: ${kleur.green('{["custom", { "script": "generateTypes" }}')}` + ) + ); + process.exit(1); + } + + const pathToClean = options.clean + ? path.relative(root, options.clean) + : undefined; + + if (pathToClean) { + report.info(`Cleaning up ${kleur.blue(pathToClean)}`); + + await del([path.resolve(root, pathToClean)]); + } + + const packageManagerExecutable = process.env.npm_execpath ?? 'npm'; + const packageManagerArgs = ['run', options.script]; + + // usr/bin/yarn -> yarn + const packageManagerName = path.basename(packageManagerExecutable); + report.info( + `Running ${kleur.blue(packageManagerName)} ${kleur.blue( + packageManagerArgs.join(' ') + )}` + ); + + try { + await spawn(packageManagerExecutable, packageManagerArgs, { + stdio: ['ignore', 'ignore', 'inherit'], + }); + } catch (e) { + report.error( + `An error occurred when running ${kleur.blue(options.script)}` + ); + process.exit(1); + } + + report.success(`Ran the ${kleur.blue(options.script)} script succesfully`); + + if (options.clean && pathToClean && !(await fs.pathExists(pathToClean))) { + report.warn( + `Custom target with the ${kleur.blue( + options.script + )} script has ${kleur.blue(options.clean)} as the ${kleur.bold( + 'clean' + )} option but this path wasn't created after running the script. Are you sure you've defined the ${kleur.bold( + 'clean' + )} path correctly?` + ); + } +} diff --git a/packages/react-native-builder-bob/src/types.ts b/packages/react-native-builder-bob/src/types.ts index 1a20240be..9a8b7419e 100644 --- a/packages/react-native-builder-bob/src/types.ts +++ b/packages/react-native-builder-bob/src/types.ts @@ -13,7 +13,12 @@ export type Input = { report: Report; }; -export type Target = 'commonjs' | 'module' | 'typescript' | 'codegen'; +export type Target = + | 'commonjs' + | 'module' + | 'typescript' + | 'codegen' + | 'custom'; export type Options = { source?: string;