Skip to content

Commit acf792c

Browse files
authoredJan 15, 2024
feat: log files in selected directory and add option to replace them (#2100)
* feat: log files in selected directory and option to replace them * feat: add `--replace-directory` option * fix: tests * fix: log everything at once
1 parent d436bfe commit acf792c

File tree

5 files changed

+92
-13
lines changed

5 files changed

+92
-13
lines changed
 

‎__e2e__/init.test.ts

+20-2
Original file line numberDiff line numberDiff line change
@@ -46,15 +46,33 @@ if (process.platform === 'win32') {
4646
templatePath = `file://${templatePath}`;
4747
}
4848

49-
test('init fails if the directory already exists', () => {
49+
test('init fails if the directory already exists and --replace-directory false', () => {
5050
fs.mkdirSync(path.join(DIR, PROJECT_NAME));
5151

52-
const {stderr} = runCLI(DIR, ['init', PROJECT_NAME], {expectedFailure: true});
52+
const {stderr} = runCLI(
53+
DIR,
54+
['init', PROJECT_NAME, '--replace-directory', 'false'],
55+
{expectedFailure: true},
56+
);
57+
5358
expect(stderr).toContain(
5459
`error Cannot initialize new project because directory "${PROJECT_NAME}" already exists.`,
5560
);
5661
});
5762

63+
test('init should ask and print files in directory if exist', () => {
64+
fs.mkdirSync(path.join(DIR, PROJECT_NAME));
65+
fs.writeFileSync(path.join(DIR, PROJECT_NAME, 'package.json'), '{}');
66+
67+
const {stdout, stderr} = runCLI(DIR, ['init', PROJECT_NAME]);
68+
69+
expect(stdout).toContain(`Do you want to replace existing files?`);
70+
expect(stderr).toContain(
71+
`warn The directory ${PROJECT_NAME} contains files that will be overwritten:`,
72+
);
73+
expect(stderr).toContain(`package.json`);
74+
});
75+
5876
test('init should prompt for the project name', () => {
5977
const {stdout} = runCLI(DIR, ['init']);
6078

‎docs/commands.md

+5
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ Skip dependencies installation
101101
Determine if CocoaPods should be installed when initializing a project. If set to `true` it will install pods, if set to `false`, it will skip the step entirely. If not used, prompt will be displayed
102102

103103
#### `--npm`
104+
104105
> [!WARNING]
105106
> `--npm` is deprecated and will be removed in the future. Please use `--pm npm` instead.
106107
@@ -121,6 +122,10 @@ Create project with custom package name for Android and bundle identifier for iO
121122

122123
Skip git repository initialization.
123124

125+
#### `--replace-directory <string>`
126+
127+
Replaces the directory if it already exists
128+
124129
### `upgrade`
125130

126131
Usage:

‎packages/cli/src/commands/init/errors/DirectoryAlreadyExistsError.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import {CLIError} from '@react-native-community/cli-tools';
33
export default class DirectoryAlreadyExistsError extends CLIError {
44
constructor(directory: string) {
55
super(
6-
`Cannot initialize new project because directory "${directory}" already exists.`,
6+
`Cannot initialize new project because directory "${directory}" already exists. Please remove or rename the directory and try again.`,
77
);
88
}
99
}

‎packages/cli/src/commands/init/index.ts

+4
Original file line numberDiff line numberDiff line change
@@ -56,5 +56,9 @@ export default {
5656
name: '--skip-git-init',
5757
description: 'Skip git repository initialization',
5858
},
59+
{
60+
name: '--replace-directory [boolean]',
61+
description: 'Replaces the directory if it already exists.',
62+
},
5963
],
6064
};

‎packages/cli/src/commands/init/init.ts

+62-10
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
import os from 'os';
22
import path from 'path';
3-
import fs from 'fs-extra';
3+
import fs, {readdirSync} from 'fs-extra';
44
import {validateProjectName} from './validate';
55
import chalk from 'chalk';
6-
import DirectoryAlreadyExistsError from './errors/DirectoryAlreadyExistsError';
76
import printRunInstructions from './printRunInstructions';
87
import {
98
CLIError,
@@ -35,6 +34,7 @@ import {
3534
} from './git';
3635
import semver from 'semver';
3736
import {executeCommand} from '../../tools/executeCommand';
37+
import DirectoryAlreadyExistsError from './errors/DirectoryAlreadyExistsError';
3838

3939
const DEFAULT_VERSION = 'latest';
4040

@@ -51,6 +51,7 @@ type Options = {
5151
installPods?: string | boolean;
5252
platformName?: string;
5353
skipGitInit?: boolean;
54+
replaceDirectory?: string | boolean;
5455
};
5556

5657
interface TemplateOptions {
@@ -65,10 +66,12 @@ interface TemplateOptions {
6566
packageName?: string;
6667
installCocoaPods?: string | boolean;
6768
version?: string;
69+
replaceDirectory?: string | boolean;
6870
}
6971

7072
interface TemplateReturnType {
7173
didInstallPods?: boolean;
74+
replaceDirectory?: string | boolean;
7275
}
7376

7477
// Here we are defining explicit version of Yarn to be used in the new project because in some cases providing `3.x` don't work.
@@ -101,8 +104,58 @@ function doesDirectoryExist(dir: string) {
101104
return fs.existsSync(dir);
102105
}
103106

104-
async function setProjectDirectory(directory: string) {
107+
function getConflictsForDirectory(directory: string) {
108+
return readdirSync(directory);
109+
}
110+
111+
async function setProjectDirectory(
112+
directory: string,
113+
replaceDirectory: string,
114+
) {
115+
const directoryExists = doesDirectoryExist(directory);
116+
117+
if (replaceDirectory === 'false' && directoryExists) {
118+
throw new DirectoryAlreadyExistsError(directory);
119+
}
120+
121+
let deleteDirectory = false;
122+
123+
if (replaceDirectory === 'true' && directoryExists) {
124+
deleteDirectory = true;
125+
} else if (directoryExists) {
126+
const conflicts = getConflictsForDirectory(directory);
127+
128+
if (conflicts.length > 0) {
129+
let warnMessage = `The directory ${chalk.bold(
130+
directory,
131+
)} contains files that will be overwritten:\n`;
132+
133+
for (const conflict of conflicts) {
134+
warnMessage += ` ${conflict}\n`;
135+
}
136+
137+
logger.warn(warnMessage);
138+
139+
const {replace} = await prompt({
140+
type: 'confirm',
141+
name: 'replace',
142+
message: 'Do you want to replace existing files?',
143+
});
144+
145+
deleteDirectory = replace;
146+
147+
if (!replace) {
148+
throw new DirectoryAlreadyExistsError(directory);
149+
}
150+
}
151+
}
152+
105153
try {
154+
if (deleteDirectory) {
155+
fs.removeSync(directory);
156+
}
157+
158+
fs.mkdirSync(directory, {recursive: true});
106159
process.chdir(directory);
107160
} catch (error) {
108161
throw new CLIError(
@@ -145,6 +198,7 @@ async function createFromTemplate({
145198
skipInstall,
146199
packageName,
147200
installCocoaPods,
201+
replaceDirectory,
148202
}: TemplateOptions): Promise<TemplateReturnType> {
149203
logger.debug('Initializing new project');
150204
// Only print out the banner if we're not in a CI
@@ -173,7 +227,10 @@ async function createFromTemplate({
173227
// if the project with the name already has cache, remove the cache to avoid problems with pods installation
174228
cacheManager.removeProjectCache(projectName);
175229

176-
const projectDirectory = await setProjectDirectory(directory);
230+
const projectDirectory = await setProjectDirectory(
231+
directory,
232+
String(replaceDirectory),
233+
);
177234

178235
const loader = getLoader({text: 'Downloading template'});
179236
const templateSourceDir = fs.mkdtempSync(
@@ -361,6 +418,7 @@ async function createProject(
361418
packageName: options.packageName,
362419
installCocoaPods: options.installPods,
363420
version,
421+
replaceDirectory: options.replaceDirectory,
364422
});
365423
}
366424

@@ -406,12 +464,6 @@ export default (async function initialize(
406464
return;
407465
}
408466

409-
if (doesDirectoryExist(projectFolder)) {
410-
throw new DirectoryAlreadyExistsError(directoryName);
411-
} else {
412-
fs.mkdirSync(projectFolder, {recursive: true});
413-
}
414-
415467
let shouldBumpYarnVersion = true;
416468
let shouldCreateGitRepository = false;
417469

0 commit comments

Comments
 (0)
Please sign in to comment.