Skip to content

Commit d709c04

Browse files
committed
refactor: init now expects templates to have react-native version set
For 0.75's previous RCs, we didn't have the infrastructure in place to tag @react-native-community/templates 0.75-stable branch correctly, as well as publishing the corresponding version to npm. For example: If @react-native-community/[email protected] is the latest version published, and I specified --version next: I'd expect 0.75.1 of the template to be used with 0.75.0-rc.1 of react-native. The downside of this approach is, if you init an older version then things might not work. You'd have to specify the matching template using --template @react-native-community/template@<some version>. Given that init is intended for new projects this isn't a concern for most users. We could look at abusing the `scripts` field to expose the corresponding version of react native in the npm registry: https://registry.npmjs.org/@react-native-community/template Something like: "scripts": { "version": "0.75.1-rc.2" }
1 parent c1e7263 commit d709c04

File tree

3 files changed

+30
-193
lines changed

3 files changed

+30
-193
lines changed

packages/cli/src/commands/init/__tests__/editTemplate.test.ts

Lines changed: 0 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,6 @@ import {
1010
replacePlaceholderWithPackageName,
1111
validatePackageName,
1212
replaceNameInUTF8File,
13-
updateDependencies,
14-
normalizeReactNativeDeps,
1513
} from '../editTemplate';
1614
import semver from 'semver';
1715

@@ -202,67 +200,6 @@ describe('changePlaceholderInTemplate', () => {
202200
);
203201
});
204202

205-
const samplePackageJson: string = `{
206-
"name": "HelloWorld",
207-
"version": "0.0.1",
208-
"private": true,
209-
"scripts": {
210-
"android": "react-native run-android",
211-
"ios": "react-native run-ios",
212-
"lint": "eslint .",
213-
"start": "react-native start",
214-
"test": "jest"
215-
},
216-
"dependencies": {
217-
"react": "19.0.0-rc-fb9a90fa48-20240614",
218-
"react-native": "1000.0.0"
219-
},
220-
"devDependencies": {
221-
"@babel/core": "^7.20.0",
222-
"@babel/preset-env": "^7.20.0",
223-
"@babel/runtime": "^7.20.0",
224-
"@react-native/babel-preset": "0.75.0-main",
225-
"@react-native/eslint-config": "0.75.0-main",
226-
"@react-native/metro-config": "0.75.0-main",
227-
"@react-native/typescript-config": "0.75.0-main",
228-
"@types/react": "^18.2.6",
229-
"@types/react-test-renderer": "^18.0.0",
230-
"babel-jest": "^29.6.3",
231-
"eslint": "^8.19.0",
232-
"jest": "^29.6.3",
233-
"prettier": "2.8.8",
234-
"react-test-renderer": "19.0.0-rc-fb9a90fa48-20240614",
235-
"typescript": "5.0.4"
236-
},
237-
"engines": {
238-
"node": ">=18"
239-
}
240-
}`;
241-
242-
describe('updateDependencies', () => {
243-
beforeEach(() => {
244-
jest.spyOn(process, 'cwd').mockImplementation(() => testPath);
245-
jest.spyOn(fs, 'writeFileSync');
246-
jest.spyOn(fs, 'readFileSync').mockImplementation(() => samplePackageJson);
247-
});
248-
249-
afterEach(() => {
250-
jest.restoreAllMocks();
251-
});
252-
253-
it('updates react-native', () => {
254-
updateDependencies({
255-
dependencies: {
256-
'react-native': '0.75.0',
257-
},
258-
});
259-
expect(fs.writeFileSync as jest.Mock).toHaveBeenCalledWith(
260-
expect.anything(),
261-
samplePackageJson.replace('1000.0.0', '0.75.0'),
262-
);
263-
});
264-
});
265-
266203
describe('replacePlaceholderWithPackageName', () => {
267204
beforeEach(() => {
268205
jest.spyOn(process, 'cwd').mockImplementation(() => testPath);
@@ -432,25 +369,3 @@ describe('replaceNameInUTF8File', () => {
432369
expect(fsWriteFileSpy).toHaveBeenCalledTimes(0);
433370
});
434371
});
435-
436-
describe('normalizeReactNativeDeps', () => {
437-
it('returns only @react-native/* dependencies updated to a specific version', () => {
438-
const devDependencies = {
439-
'@babel/core': '^7.20.0',
440-
'@react-native/babel-preset': '0.75.0-main',
441-
'@react-native/eslint-config': '0.75.0-main',
442-
'@react-native/metro-config': '0.75.0-main',
443-
'@react-native/typescript-config': '0.75.0-main',
444-
'@types/react': '^18.2.6',
445-
'@types/react-test-renderer': '^18.0.0',
446-
eslint: '^8.19.0',
447-
'react-test-renderer': '19.0.0-rc-fb9a90fa48-20240614',
448-
};
449-
expect(normalizeReactNativeDeps(devDependencies, '0.75.0')).toMatchObject({
450-
'@react-native/babel-preset': '0.75.0',
451-
'@react-native/eslint-config': '0.75.0',
452-
'@react-native/metro-config': '0.75.0',
453-
'@react-native/typescript-config': '0.75.0',
454-
});
455-
});
456-
});

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

Lines changed: 0 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,6 @@ interface PlaceholderConfig {
1515
packageName?: string;
1616
}
1717

18-
interface NpmPackageDependencies {
19-
dependencies?: {
20-
[name: string]: string;
21-
};
22-
devDependencies?: {
23-
[name: string]: string;
24-
};
25-
}
26-
2718
/**
2819
TODO: This is a default placeholder for title in react-native template.
2920
We should get rid of this once custom templates adapt `placeholderTitle` in their configurations.
@@ -235,35 +226,6 @@ export async function replacePlaceholderWithPackageName({
235226
}
236227
}
237228

238-
export function updateDependencies(update: NpmPackageDependencies) {
239-
logger.debug('Updating package.json dependencies:');
240-
const pkgJsonPath = path.join(process.cwd(), 'package.json');
241-
242-
const pkgJson = JSON.parse(fs.readFileSync(pkgJsonPath, 'utf8'));
243-
244-
for (const [pkg, value] of Object.entries(update.dependencies ?? {})) {
245-
const old = pkgJson.dependencies[pkg];
246-
if (old) {
247-
logger.debug(`${pkg}: ${old}${value}`);
248-
} else {
249-
logger.debug(`${pkg}: ${value}`);
250-
}
251-
pkgJson.dependencies[pkg] = value;
252-
}
253-
254-
for (const [pkg, value] of Object.entries(update.devDependencies ?? {})) {
255-
const old = pkgJson.devDependencies[pkg];
256-
if (old) {
257-
logger.debug(`${pkg}: ${old}${value}`);
258-
} else {
259-
logger.debug(`${pkg}: ${value}`);
260-
}
261-
pkgJson.devDependencies[pkg] = value;
262-
}
263-
264-
fs.writeFileSync(pkgJsonPath, JSON.stringify(pkgJson, null, 2));
265-
}
266-
267229
export async function changePlaceholderInTemplate({
268230
projectName,
269231
placeholderName,
@@ -307,25 +269,3 @@ export async function changePlaceholderInTemplate({
307269
}
308270
}
309271
}
310-
311-
type Packages = {
312-
[pkgs: string]: string;
313-
};
314-
315-
const REACT_NATIVE_SCOPE = '@react-native/';
316-
317-
/**
318-
* Packages that are scoped under @react-native need a consistent version
319-
*/
320-
export function normalizeReactNativeDeps(
321-
deps: Packages | undefined,
322-
version: string,
323-
): Packages {
324-
const updated: Packages = {};
325-
for (const key of Object.keys(deps ?? {}).filter((pkg) =>
326-
pkg.startsWith(REACT_NATIVE_SCOPE),
327-
)) {
328-
updated[key] = version;
329-
}
330-
return updated;
331-
}

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

Lines changed: 30 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,7 @@ import {
1919
copyTemplate,
2020
executePostInitScript,
2121
} from './template';
22-
import {
23-
changePlaceholderInTemplate,
24-
updateDependencies,
25-
normalizeReactNativeDeps,
26-
} from './editTemplate';
22+
import {changePlaceholderInTemplate} from './editTemplate';
2723
import * as PackageManager from '../../tools/packageManager';
2824
import banner from './banner';
2925
import TemplateAndVersionError from './errors/TemplateAndVersionError';
@@ -220,7 +216,6 @@ async function createFromTemplate({
220216
installCocoaPods,
221217
replaceDirectory,
222218
yarnConfigOptions,
223-
version,
224219
}: TemplateOptions): Promise<TemplateReturnType> {
225220
logger.debug('Initializing new project');
226221
// Only print out the banner if we're not in a CI
@@ -291,35 +286,6 @@ async function createFromTemplate({
291286
packageName,
292287
});
293288

294-
// Update the react-native dependency if using the new @react-native-community/template.
295-
// We can figure this out as it ships with [email protected] set to a dummy version.
296-
const templatePackageJson = JSON.parse(
297-
fs.readFileSync(path.join(process.cwd(), 'package.json'), 'utf8'),
298-
);
299-
if (templatePackageJson.dependencies?.['react-native'] === '1000.0.0') {
300-
// Since 0.75, the version of all @react-native/ packages are pinned to the version of
301-
// react native. Enforce this when updating versions.
302-
const concreteVersion = await npmResolveConcreteVersion(
303-
'react-native',
304-
version,
305-
);
306-
updateDependencies({
307-
dependencies: {
308-
'react-native': concreteVersion,
309-
...normalizeReactNativeDeps(
310-
templatePackageJson.dependencies,
311-
concreteVersion,
312-
),
313-
},
314-
devDependencies: {
315-
...normalizeReactNativeDeps(
316-
templatePackageJson.devDependencies,
317-
concreteVersion,
318-
),
319-
},
320-
});
321-
}
322-
323289
if (packageManager === 'yarn' && shouldBumpYarnVersion) {
324290
await bumpYarnVersion(false, projectDirectory);
325291
}
@@ -462,11 +428,20 @@ async function createTemplateUri(
462428
version,
463429
);
464430

431+
const validSemverVersion = semver.coerce(version)?.version;
432+
433+
if (validSemverVersion == null) {
434+
logger.warn(
435+
`Version: '${version}' is in-valid semver, could not determine which template to used.`,
436+
);
437+
process.exit(1);
438+
}
439+
465440
// Test #2: Does the react-native@version package *not* have a template embedded. We know that
466441
// this applies to all version before 0.75. The 1st release candidate is the minimal
467442
// version that has no template.
468443
const useLegacyTemplate = semver.lt(
469-
version,
444+
validSemverVersion,
470445
TEMPLATE_COMMUNITY_REACT_NATIVE_VERSION,
471446
);
472447

@@ -501,25 +476,32 @@ async function createProject(
501476
): Promise<TemplateReturnType> {
502477
// Handle these cases (when community template is published and react-native >= 0.75
503478
//
504-
// +==================================================================+==========+==============+
505-
// | Arguments | Template | React Native |
506-
// +==================================================================+==========+==============+
507-
// | <None> | latest | latest |
508-
// +------------------------------------------------------------------+----------+--------------+
509-
// | --version 0.75.0 | 0.75.0 | 0.75.0 |
510-
// +------------------------------------------------------------------+----------+--------------+
511-
// | --template @react-native-community/[email protected] | 0.75.1 | latest |
512-
// +------------------------------------------------------------------+----------+--------------+
513-
// | --template @react-native-community/[email protected] --version 0.75| 0.75.1 | 0.75.x |
514-
// +------------------------------------------------------------------+----------+--------------+
479+
// +==================================================================+==========+===================+
480+
// | Arguments | Template | React Native |
481+
// +==================================================================+==========+===================+
482+
// | <None> | 0.74.x | 0.74.5 (latest) |
483+
// +------------------------------------------------------------------+----------+-------------------+
484+
// | --version next | 0.75.x | 0.75.0-rc.1 (next)|
485+
// +------------------------------------------------------------------+----------+-------------------+
486+
// | --version 0.75.0 | 0.75.x | 0.75.0 |
487+
// +------------------------------------------------------------------+----------+-------------------+
488+
// | --template @react-native-community/[email protected] | 0.75.1 | latest |
489+
// +------------------------------------------------------------------+----------+-------------------+
490+
// | --template @react-native-community/[email protected] --version 0.75| 0.75.1 | 0.75.x |
491+
// +------------------------------------------------------------------+----------+-------------------+
515492
//
516493
// 1. If you specify `--version 0.75.0` and `@react-native-community/[email protected]` is *NOT*
517494
// published, then `init` will exit and suggest explicitly using the `--template` argument.
518495
//
519496
// 2. `--template` will always win over `--version` for the template.
520497
//
521498
// 3. For version < 0.75, the template ships with react-native.
522-
const templateUri = await createTemplateUri(options, version);
499+
const v = semver.parse(version);
500+
if (v == null) {
501+
throw new Error(`--version=${version} isn't valid SEMVER`);
502+
}
503+
const templateVersion = `${v.major}.${v.minor}`;
504+
const templateUri = await createTemplateUri(options, templateVersion);
523505

524506
return createFromTemplate({
525507
projectName,

0 commit comments

Comments
 (0)