Skip to content

Commit 6461dcd

Browse files
blakeffacebook-github-bot
authored andcommitted
Port: @react-native-community/cli#clean → @react-native/core-cli-utils#clean (#43287)
Summary: Pull Request resolved: #43287 Move react-native-community/cli clean into core per RFC-0759. Provides: - android - metro - npm - bun - watchman - yarn - cocoapods These tasks are used to clear up caching artefacts in React Native projects. This is going to be called by the `react-native-community/cli` once we publish these in an npm package. Changelog: [General][Added] RFC-0759 Move cli clean into core Reviewed By: cipolleschi Differential Revision: D53997878 fbshipit-source-id: 56907be714184abecc8e3ef677ffc83e9ee7b54d
1 parent 5f45700 commit 6461dcd

File tree

6 files changed

+324
-0
lines changed

6 files changed

+324
-0
lines changed

packages/core-cli-utils/README.md

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# @react-native/core-cli-utils
2+
3+
![npm package](https://img.shields.io/npm/v/@react-native/core-cli-utils?color=brightgreen&label=npm%20package)
4+
5+
A collection of utilites to help Frameworks build their React Native CLI tooling. This is not intended to be used directly use users of React Native.
6+
7+
## Usage
8+
9+
```js
10+
import { Command } from 'commander';
11+
import cli from '@react-native/core-cli-utils';
12+
import debug from 'debug';
13+
14+
const android = new Command('android');
15+
16+
const frameworkFindsAndroidSrcDir = "...";
17+
const tasks = cli.clean.android(frameworkFindsAndroidSrcDir);
18+
const log = debug('fancy-framework:android');
19+
20+
android
21+
.command('clean')
22+
.description(cli.clean.android)
23+
.action(async () => {
24+
const log = debug('fancy-framework:android:clean');
25+
log(`🧹 let me clean your Android caches`);
26+
// Add other caches your framework needs besides the normal React Native caches
27+
// here.
28+
for (const task of tasks) {
29+
try {
30+
log(`\t ${task.label}`);
31+
// See: https://github.com/sindresorhus/execa#lines
32+
const {stdout} = await task.action({ lines: true })
33+
log(stdout.join('\n\tGradle: '));
34+
} catch (e) {
35+
log(`\t ⚠️ whoops: ${e.message}`);
36+
}
37+
}
38+
});
39+
```
40+
41+
And you'd be using it like this:
42+
43+
```bash
44+
$ ./fancy-framework android clean
45+
🧹 let me clean your Android caches
46+
Gradle: // a bunch of gradle output
47+
Gradle: ....
48+
```
49+
50+
## Contributing
51+
52+
Changes to this package can be made locally and linked against your app. Please see the [Contributing guide](https://reactnative.dev/contributing/overview#contributing-code).

packages/core-cli-utils/index.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
/**
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
* @flow strict-local
8+
* @format
9+
* @oncall react_native
10+
*/
11+
12+
import {tasks as clean} from './private/clean.js';
13+
14+
export default {
15+
clean,
16+
};

packages/core-cli-utils/package.json

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
{
2+
"name": "@react-native/core-cli-utils",
3+
"version": "0.74.0",
4+
"description": "React Native CLI library for Frameworks to build on",
5+
"main": "index.js",
6+
"license": "MIT",
7+
"repository": {
8+
"type": "git",
9+
"url": "https://github.com/facebook/react-native.git",
10+
"directory": "packages/core-cli-utils"
11+
},
12+
"exports": {
13+
".": "./index.js",
14+
"./package.json": "./package.json"
15+
},
16+
"homepage": "https://github.com/facebook/react-native/tree/HEAD/packages/core-cli-utils#readme",
17+
"keywords": [
18+
"cli-utils",
19+
"react-native"
20+
],
21+
"bugs": "https://github.com/facebook/react-native/issues",
22+
"engines": {
23+
"node": ">=18"
24+
},
25+
"files": [
26+
"dist"
27+
],
28+
"dependencies": {},
29+
"devDependencies": {}
30+
}
Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
/**
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
* @flow strict-local
8+
* @format
9+
* @oncall react_native
10+
*/
11+
12+
import type {Task} from './types';
13+
import type {Options as ExecaOptions} from 'execa';
14+
15+
import {isMacOS, isWindows, task} from './utils';
16+
import execa from 'execa';
17+
import {existsSync, readdirSync, rm} from 'fs';
18+
import os from 'os';
19+
import path from 'path';
20+
21+
type CleanTasks = {
22+
android: (androidSrcDir: ?string) => Task[],
23+
metro: () => Task[],
24+
npm: (projectRootDir: string, verifyCache?: boolean) => Task[],
25+
bun: (projectRootDir: string) => Task[],
26+
watchman: (projectRootDir: string) => Task[],
27+
yarn: (projectRootDir: string) => Task[],
28+
cocoapods?: (projectRootDir: string) => Task[],
29+
};
30+
31+
const rmrf = (pathname: string) => {
32+
if (!existsSync(pathname)) {
33+
return;
34+
}
35+
rm(pathname, {maxRetries: 3, recursive: true, force: true});
36+
};
37+
38+
/**
39+
* Removes the contents of a directory matching a given pattern, but keeps the directory.
40+
* @private
41+
*/
42+
export function deleteDirectoryContents(
43+
directory: string,
44+
filePattern: RegExp,
45+
): Task['action'] {
46+
return async function deleteDirectoryContentsAction() {
47+
const base = path.dirname(directory);
48+
const files = readdirSync(base).filter((filename: string) =>
49+
filePattern.test(filename),
50+
);
51+
for (const filename of files) {
52+
rmrf(path.join(base, filename));
53+
}
54+
};
55+
}
56+
57+
/**
58+
* Removes a directory recursively.
59+
* @private
60+
*/
61+
export function deleteDirectory(directory: string): Task['action'] {
62+
return async function cleanDirectoryAction() {
63+
rmrf(directory);
64+
};
65+
}
66+
67+
/**
68+
* Deletes the contents of the tmp directory matching a given pattern.
69+
* @private
70+
*/
71+
export function deleteTmpDirectoryContents(
72+
filepattern: RegExp,
73+
): ReturnType<typeof deleteDirectoryContents> {
74+
return deleteDirectoryContents(os.tmpdir(), filepattern);
75+
}
76+
77+
// The tasks that cleanup various build artefacts.
78+
export const tasks: CleanTasks = {
79+
/**
80+
* Cleans up the Android Gradle cache
81+
*/
82+
android: (androidSrcDir: ?string) => [
83+
task('🧹 Clean Gradle cache', async function gradle(opts?: ExecaOptions) {
84+
const gradlew = path.join(
85+
androidSrcDir ?? 'android',
86+
isWindows ? 'gradlew.bat' : 'gradlew',
87+
);
88+
89+
if (!existsSync(gradlew)) {
90+
return;
91+
}
92+
const script = path.basename(gradlew);
93+
const cwd = path.dirname(gradlew);
94+
await execa(isWindows ? script : `./${script}`, ['clean'], {
95+
cwd,
96+
...opts,
97+
});
98+
}),
99+
],
100+
101+
/**
102+
* Agressively cleans up all Metro caches.
103+
*/
104+
metro: () => [
105+
task('🧹 Clean Metro cache', deleteTmpDirectoryContents(/^metro-.+/)),
106+
task('🧹 Clean Haste cache', deleteTmpDirectoryContents(/^haste-map-.+/)),
107+
task(
108+
'🧹 Clean React Native cache',
109+
deleteTmpDirectoryContents(/^react-.+/),
110+
),
111+
],
112+
113+
/**
114+
* Cleans up the `node_modules` folder and optionally garbage collects the npm cache.
115+
*/
116+
npm: (projectRootDir: string, verifyCache = false) => {
117+
const _tasks = [
118+
task(
119+
'🧹 Clean node_modules',
120+
deleteDirectory(path.join(projectRootDir, 'node_modules')),
121+
),
122+
];
123+
if (verifyCache) {
124+
_tasks.push(
125+
task('🔬 Verify npm cache', (opts?: ExecaOptions) =>
126+
execa('npm', ['cache', 'verify'], {cwd: projectRootDir, ...opts}),
127+
),
128+
);
129+
}
130+
return _tasks;
131+
},
132+
133+
/**
134+
* Cleans up the Bun cache.
135+
*/
136+
bun: (projectRootDir: string) => [
137+
task('🧹 Clean Bun cache', (opts?: ExecaOptions) =>
138+
execa('bun', ['pm', 'cache', 'rm'], {cwd: projectRootDir, ...opts}),
139+
),
140+
],
141+
142+
/**
143+
* Stops Watchman and clears its cache
144+
*/
145+
watchman: (projectRootDir: string) => [
146+
task('✋ Stop Watchman', (opts?: ExecaOptions) =>
147+
execa(isWindows ? 'tskill' : 'killall', ['watchman'], {
148+
cwd: projectRootDir,
149+
...opts,
150+
}),
151+
),
152+
task('🧹 Delete Watchman cache', (opts?: ExecaOptions) =>
153+
execa('watchman', ['watch-del-all'], {cwd: projectRootDir, ...opts}),
154+
),
155+
],
156+
157+
/**
158+
* Cleans up the Yarn cache
159+
*/
160+
yarn: (projectRootDir: string) => [
161+
task('🧹 Clean Yarn cache', (opts?: ExecaOptions) =>
162+
execa('yarn', ['cache', 'clean'], {cwd: projectRootDir, ...opts}),
163+
),
164+
],
165+
};
166+
167+
if (isMacOS) {
168+
/**
169+
* Cleans up the local and global CocoaPods cache
170+
*/
171+
tasks.cocoapods = (projectRootDir: string) => [
172+
// TODO: add project root
173+
task(
174+
'🧹 Clean CocoaPods pod cache',
175+
function removePodCache(opts?: ExecaOptions) {
176+
return execa('bundle', ['exec', 'pod', 'deintegrate'], {
177+
cwd: projectRootDir,
178+
...opts,
179+
});
180+
},
181+
),
182+
];
183+
}
184+
185+
//
186+
// Internal CLI
187+
//
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
/**
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
* @flow strict-local
8+
* @format
9+
* @oncall react_native
10+
*/
11+
12+
export type Task = {
13+
label: string,
14+
action: () => Promise<mixed>,
15+
};
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/**
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
* @flow strict-local
8+
* @format
9+
* @oncall react_native
10+
*/
11+
12+
import type {Task} from './types';
13+
14+
import os from 'os';
15+
16+
export function task(label: string, action: Task['action']): Task {
17+
return {
18+
label,
19+
action,
20+
};
21+
}
22+
23+
export const isWindows = os.platform() === 'win32';
24+
export const isMacOS = os.platform() === 'darwin';

0 commit comments

Comments
 (0)