Skip to content

Commit 55bda44

Browse files
authored
feat: check npm and github for template (react-native-community#2334)
1 parent 10bfae3 commit 55bda44

File tree

3 files changed

+96
-17
lines changed

3 files changed

+96
-17
lines changed

docs/commands.md

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ React Native CLI comes with following commands:
2222

2323
> Available since 0.60.0
2424
25-
> **IMPORTANT**: Please note that this command is not available through `react-native-cli`, hence you need to either invoke it directly from `@react-native-community/cli` or `react-native` package which proxies binary to this CLI since 0.60.0, so it's possible to use it with e.g. `npx`.
25+
> **IMPORTANT**: Please note that this command is not available through `react-native-cli`, hence you need to either invoke it directly from the `@react-native-community/cli` package which proxies binary to this CLI since 0.60.0, so it's possible to use it with e.g. `npx`.
2626
2727
Usage (with `npx`):
2828

@@ -36,7 +36,7 @@ Initialize a new React Native project named <projectName> in a directory of the
3636

3737
#### `--version <string>`
3838

39-
Shortcut for `--template react-native@version`.
39+
The version of React Native to use with the template.
4040

4141
#### `--directory <string>`
4242

@@ -58,10 +58,12 @@ Uses a custom template. Accepts following template sources:
5858
Example:
5959

6060
```sh
61-
npx react-native@latest init MyApp --template react-native-custom-template
62-
npx react-native@latest init MyApp --template file:///Users/name/template-path
63-
npx react-native@latest init MyApp --template file:///Users/name/template-name-1.0.0.tgz
64-
npx react-native@latest init MyApp --template Esemesek/react-native-new-template
61+
npx react-native-community/cli@latest init MyApp --template react-native-community/cli-custom-template
62+
npx react-native-community/cli@latest init MyApp --template file:///Users/name/template-path
63+
npx react-native-community/cli@latest init MyApp --template file:///Users/name/template-name-1.0.0.tgz
64+
npx react-native-community/cli@latest init MyApp --template Esemesek/react-native-community/cli-new-template
65+
# Use a specific version of the community template with the nightly release of React Native
66+
npx react-native-community/cli@latest init MyApp --template @react-native-community/template0.74.0 --version nightly
6567
```
6668

6769
A template is any directory or npm package that contains a `template.config.js` file in the root with the following type:

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ export default {
99
options: [
1010
{
1111
name: '--version <string>',
12-
description: 'Shortcut for `--template react-native@version`',
12+
description: 'React Native version to install in the template',
1313
},
1414
{
1515
name: '--template <string>',

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

Lines changed: 87 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import os from 'os';
22
import path from 'path';
3+
import {execSync} from 'child_process';
34
import fs, {readdirSync} from 'fs-extra';
45
import {validateProjectName} from './validate';
56
import chalk from 'chalk';
@@ -37,6 +38,9 @@ import {executeCommand} from '../../tools/executeCommand';
3738
import DirectoryAlreadyExistsError from './errors/DirectoryAlreadyExistsError';
3839

3940
const DEFAULT_VERSION = 'latest';
41+
const TEMPLATE_PACKAGE_COMMUNITY = '@react-native-community/template';
42+
const TEMPLATE_PACKAGE_LEGACY = 'react-native';
43+
const TEMPLATE_PACKAGE_LEGACY_TYPESCRIPT = 'react-native-template-typescript';
4044

4145
type Options = {
4246
template?: string;
@@ -389,21 +393,80 @@ function checkPackageManagerAvailability(
389393
return false;
390394
}
391395

392-
function createTemplateUri(options: Options, version: string): string {
393-
const isTypescriptTemplate =
394-
options.template === 'react-native-template-typescript';
396+
function getNpmRegistryUrl(): string {
397+
try {
398+
return execSync('npm config get registry').toString().trim();
399+
} catch {
400+
return 'https://registry.npmjs.org/';
401+
}
402+
}
403+
404+
async function urlExists(url: string): Promise<boolean> {
405+
try {
406+
// @ts-ignore-line: TS2304
407+
const {status} = await fetch(url, {method: 'HEAD'});
408+
return (
409+
[
410+
200, // OK
411+
301, // Moved Permanemently
412+
302, // Found
413+
304, // Not Modified
414+
307, // Temporary Redirect
415+
308, // Permanent Redirect
416+
].indexOf(status) !== -1
417+
);
418+
} catch {
419+
return false;
420+
}
421+
}
395422

396-
// This allows to correctly retrieve template uri for out of tree platforms.
397-
const platform = options.platformName || 'react-native';
423+
async function createTemplateUri(options: Options): Promise<string> {
424+
if (options.platformName) {
425+
logger.debug('User has specified an out-of-tree platform, using it');
426+
return `${options.platformName}@${options.version ?? DEFAULT_VERSION}`;
427+
}
398428

399-
if (isTypescriptTemplate) {
429+
if (options.template === TEMPLATE_PACKAGE_LEGACY_TYPESCRIPT) {
400430
logger.warn(
401431
"Ignoring custom template: 'react-native-template-typescript'. Starting from React Native v0.71 TypeScript is used by default.",
402432
);
403-
return platform;
433+
return TEMPLATE_PACKAGE_LEGACY;
434+
}
435+
436+
if (options.template) {
437+
logger.debug(`Use the user provided --template=${options.template}`);
438+
return options.template;
439+
}
440+
441+
// Figure out whether we should use the @react-native-community/template over react-native/template,
442+
// this requires 2 tests.
443+
const registryHost = getNpmRegistryUrl();
444+
445+
// Test #1: Does the @react-native-community/template@latest exist?
446+
const templateNpmRegistryUrl = `${registryHost}${TEMPLATE_PACKAGE_COMMUNITY}/latest`;
447+
const canUseCommunityTemplate = await urlExists(templateNpmRegistryUrl);
448+
449+
const reactNativeVersion = options.version ?? DEFAULT_VERSION;
450+
let gitTagFromVersion = semver.valid(reactNativeVersion);
451+
if (gitTagFromVersion != null) {
452+
// The React Native project prefixes tags with a 'v', for example v0.73.5,
453+
gitTagFromVersion = `v${gitTagFromVersion}`;
454+
} else {
455+
gitTagFromVersion = reactNativeVersion;
404456
}
405457

406-
return options.template || `${platform}@${version}`;
458+
// Test #2: Does the react-native@version package *not* have a template embedded.
459+
const reactNativeGithubTemplateUrl = `https://raw.githubusercontent.com/facebook/react-native/${gitTagFromVersion}/packages/react-native/template/package.json`;
460+
const useLegacyTemplate = await urlExists(reactNativeGithubTemplateUrl);
461+
462+
if (!useLegacyTemplate && canUseCommunityTemplate) {
463+
return `${TEMPLATE_PACKAGE_COMMUNITY}@latest`;
464+
}
465+
466+
logger.debug(
467+
`Using the legacy template because '${TEMPLATE_PACKAGE_LEGACY}' still contains a template folder`,
468+
);
469+
return `${TEMPLATE_PACKAGE_LEGACY}@${reactNativeVersion}`;
407470
}
408471

409472
async function createProject(
@@ -413,7 +476,21 @@ async function createProject(
413476
shouldBumpYarnVersion: boolean,
414477
options: Options,
415478
): Promise<TemplateReturnType> {
416-
const templateUri = createTemplateUri(options, version);
479+
// Handle these cases (when community template is published and react-native
480+
// doesn't have a template in 'react-native/template'):
481+
//
482+
// +==================================================================+==========+==============+
483+
// | Arguments | Template | React Native |
484+
// +==================================================================+==========+==============+
485+
// | <None> | latest | latest |
486+
// +------------------------------------------------------------------+----------+--------------+
487+
// | --version 0.75.0 | latest | 0.75.0 |
488+
// +------------------------------------------------------------------+----------+--------------+
489+
// | --template @react-native-community/[email protected] | 0.75.1 | latest |
490+
// +------------------------------------------------------------------+----------+--------------+
491+
// | --template @react-native-community/[email protected] --version 0.75| 0.75.1 | 0.75.x |
492+
// +------------------------------------------------------------------+----------+--------------+
493+
const templateUri = await createTemplateUri(options);
417494

418495
return createFromTemplate({
419496
projectName,
@@ -462,7 +539,7 @@ export default (async function initialize(
462539
}
463540

464541
const root = process.cwd();
465-
const version = options.version || DEFAULT_VERSION;
542+
const version = options.version ?? DEFAULT_VERSION;
466543

467544
const directoryName = path.relative(root, options.directory || projectName);
468545
const projectFolder = path.join(root, directoryName);

0 commit comments

Comments
 (0)