Skip to content

Fix template name handling #9412

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Aug 5, 2020
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions packages/create-react-app/__tests__/.eslintrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"env": {
"jest": true
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

'use strict';

const { getTemplateInstallPackage } = require('../createReactApp');

describe('getTemplateInstallPackage', () => {
it('no options gives cra-template', async () => {
await expect(getTemplateInstallPackage()).resolves.toBe('cra-template');
});

it('cra-template gives cra-template', async () => {
await expect(getTemplateInstallPackage('cra-template')).resolves.toBe(
'cra-template'
);
});

it('cra-template-typescript gives cra-template-typescript', async () => {
await expect(getTemplateInstallPackage('cra-template-typescript')).resolves.toBe(
'cra-template-typescript'
);
});

it('typescript gives cra-template-typescript', async () => {
await expect(getTemplateInstallPackage('typescript')).resolves.toBe(
'cra-template-typescript'
);
});

it('typescript@next gives cra-template-typescript@next', async () => {
await expect(getTemplateInstallPackage('cra-template-typescript@next')).resolves.toBe(
'cra-template-typescript@next'
);
});

it('cra-template@next gives cra-template@next', async () => {
await expect(getTemplateInstallPackage('cra-template@next')).resolves.toBe(
'cra-template@next'
);
});

it('cra-template-typescript@next gives cra-template-typescript@next', async () => {
await expect(getTemplateInstallPackage('cra-template-typescript@next')).resolves.toBe(
'cra-template-typescript@next'
);
});

it('@iansu gives @iansu/cra-template', async () => {
await expect(getTemplateInstallPackage('@iansu')).resolves.toBe(
'@iansu/cra-template'
);
});

it('@iansu/cra-template gives @iansu/cra-template', async () => {
await expect(
getTemplateInstallPackage('@iansu/cra-template')
).resolves.toBe('@iansu/cra-template');
});

it('@iansu/cra-template@next gives @iansu/cra-template@next', async () => {
await expect(
getTemplateInstallPackage('@iansu/cra-template@next')
).resolves.toBe('@iansu/cra-template@next');
});

it('@iansu/cra-template-typescript@next gives @iansu/cra-template-typescript@next', async () => {
await expect(getTemplateInstallPackage('@iansu/cra-template-typescript@next')).resolves.toBe(
'@iansu/cra-template-typescript@next'
);
});

it('http://example.com/cra-template.tar.gz gives http://example.com/cra-template.tar.gz', async () => {
await expect(
getTemplateInstallPackage('http://example.com/cra-template.tar.gz')
).resolves.toBe('http://example.com/cra-template.tar.gz');
});
});
348 changes: 183 additions & 165 deletions packages/create-react-app/createReactApp.js
Original file line number Diff line number Diff line change
@@ -51,178 +51,190 @@ const packageJson = require('./package.json');

let projectName;

const program = new commander.Command(packageJson.name)
.version(packageJson.version)
.arguments('<project-directory>')
.usage(`${chalk.green('<project-directory>')} [options]`)
.action(name => {
projectName = name;
})
.option('--verbose', 'print additional logs')
.option('--info', 'print environment debug info')
.option(
'--scripts-version <alternative-package>',
'use a non-standard version of react-scripts'
)
.option(
'--template <path-to-template>',
'specify a template for the created project'
)
.option('--use-npm')
.option('--use-pnp')
.allowUnknownOption()
.on('--help', () => {
console.log(` Only ${chalk.green('<project-directory>')} is required.`);
console.log();
console.log(
` A custom ${chalk.cyan('--scripts-version')} can be one of:`
);
console.log(` - a specific npm version: ${chalk.green('0.8.2')}`);
console.log(` - a specific npm tag: ${chalk.green('@next')}`);
console.log(
` - a custom fork published on npm: ${chalk.green(
'my-react-scripts'
)}`
);
console.log(
` - a local path relative to the current working directory: ${chalk.green(
'file:../my-react-scripts'
)}`
);
console.log(
` - a .tgz archive: ${chalk.green(
'https://mysite.com/my-react-scripts-0.8.2.tgz'
)}`
);
function init() {
const program = new commander.Command(packageJson.name)
.version(packageJson.version)
.arguments('<project-directory>')
.usage(`${chalk.green('<project-directory>')} [options]`)
.action(name => {
projectName = name;
})
.option('--verbose', 'print additional logs')
.option('--info', 'print environment debug info')
.option(
'--scripts-version <alternative-package>',
'use a non-standard version of react-scripts'
)
.option(
'--template <path-to-template>',
'specify a template for the created project'
)
.option('--use-npm')
.option('--use-pnp')
.allowUnknownOption()
.on('--help', () => {
console.log(
` Only ${chalk.green('<project-directory>')} is required.`
);
console.log();
console.log(
` A custom ${chalk.cyan('--scripts-version')} can be one of:`
);
console.log(` - a specific npm version: ${chalk.green('0.8.2')}`);
console.log(` - a specific npm tag: ${chalk.green('@next')}`);
console.log(
` - a custom fork published on npm: ${chalk.green(
'my-react-scripts'
)}`
);
console.log(
` - a local path relative to the current working directory: ${chalk.green(
'file:../my-react-scripts'
)}`
);
console.log(
` - a .tgz archive: ${chalk.green(
'https://mysite.com/my-react-scripts-0.8.2.tgz'
)}`
);
console.log(
` - a .tar.gz archive: ${chalk.green(
'https://mysite.com/my-react-scripts-0.8.2.tar.gz'
)}`
);
console.log(
` It is not needed unless you specifically want to use a fork.`
);
console.log();
console.log(` A custom ${chalk.cyan('--template')} can be one of:`);
console.log(
` - a custom template published on npm: ${chalk.green(
'cra-template-typescript'
)}`
);
console.log(
` - a local path relative to the current working directory: ${chalk.green(
'file:../my-custom-template'
)}`
);
console.log(
` - a .tgz archive: ${chalk.green(
'https://mysite.com/my-custom-template-0.8.2.tgz'
)}`
);
console.log(
` - a .tar.gz archive: ${chalk.green(
'https://mysite.com/my-custom-template-0.8.2.tar.gz'
)}`
);
console.log();
console.log(
` If you have any problems, do not hesitate to file an issue:`
);
console.log(
` ${chalk.cyan(
'https://github.com/facebook/create-react-app/issues/new'
)}`
);
console.log();
})
.parse(process.argv);

if (program.info) {
console.log(chalk.bold('\nEnvironment Info:'));
console.log(
` - a .tar.gz archive: ${chalk.green(
'https://mysite.com/my-react-scripts-0.8.2.tar.gz'
)}`
`\n current version of ${packageJson.name}: ${packageJson.version}`
);
console.log(` running from ${__dirname}`);
return envinfo
.run(
{
System: ['OS', 'CPU'],
Binaries: ['Node', 'npm', 'Yarn'],
Browsers: [
'Chrome',
'Edge',
'Internet Explorer',
'Firefox',
'Safari',
],
npmPackages: ['react', 'react-dom', 'react-scripts'],
npmGlobalPackages: ['create-react-app'],
},
{
duplicates: true,
showNotFound: true,
}
)
.then(console.log);
}

if (typeof projectName === 'undefined') {
console.error('Please specify the project directory:');
console.log(
` It is not needed unless you specifically want to use a fork.`
` ${chalk.cyan(program.name())} ${chalk.green('<project-directory>')}`
);
console.log();
console.log(` A custom ${chalk.cyan('--template')} can be one of:`);
console.log(
` - a custom template published on npm: ${chalk.green(
'cra-template-typescript'
)}`
);
console.log(
` - a local path relative to the current working directory: ${chalk.green(
'file:../my-custom-template'
)}`
);
console.log(
` - a .tgz archive: ${chalk.green(
'https://mysite.com/my-custom-template-0.8.2.tgz'
)}`
);
console.log('For example:');
console.log(
` - a .tar.gz archive: ${chalk.green(
'https://mysite.com/my-custom-template-0.8.2.tar.gz'
)}`
` ${chalk.cyan(program.name())} ${chalk.green('my-react-app')}`
);
console.log();
console.log(
` If you have any problems, do not hesitate to file an issue:`
`Run ${chalk.cyan(`${program.name()} --help`)} to see all options.`
);
console.log(
` ${chalk.cyan(
'https://github.com/facebook/create-react-app/issues/new'
)}`
);
console.log();
})
.parse(process.argv);
process.exit(1);
}

if (program.info) {
console.log(chalk.bold('\nEnvironment Info:'));
console.log(
`\n current version of ${packageJson.name}: ${packageJson.version}`
);
console.log(` running from ${__dirname}`);
return envinfo
.run(
{
System: ['OS', 'CPU'],
Binaries: ['Node', 'npm', 'Yarn'],
Browsers: ['Chrome', 'Edge', 'Internet Explorer', 'Firefox', 'Safari'],
npmPackages: ['react', 'react-dom', 'react-scripts'],
npmGlobalPackages: ['create-react-app'],
},
{
duplicates: true,
showNotFound: true,
// We first check the registry directly via the API, and if that fails, we try
// the slower `npm view [package] version` command.
//
// This is important for users in environments where direct access to npm is
// blocked by a firewall, and packages are provided exclusively via a private
// registry.
checkForLatestVersion()
.catch(() => {
try {
return execSync('npm view create-react-app version').toString().trim();
} catch (e) {
return null;
}
)
.then(console.log);
}

if (typeof projectName === 'undefined') {
console.error('Please specify the project directory:');
console.log(
` ${chalk.cyan(program.name())} ${chalk.green('<project-directory>')}`
);
console.log();
console.log('For example:');
console.log(` ${chalk.cyan(program.name())} ${chalk.green('my-react-app')}`);
console.log();
console.log(
`Run ${chalk.cyan(`${program.name()} --help`)} to see all options.`
);
process.exit(1);
})
.then(latest => {
if (latest && semver.lt(packageJson.version, latest)) {
console.log();
console.error(
chalk.yellow(
`You are running \`create-react-app\` ${packageJson.version}, which is behind the latest release (${latest}).\n\n` +
'We no longer support global installation of Create React App.'
)
);
console.log();
console.log(
'Please remove any global installs with one of the following commands:\n' +
'- npm uninstall -g create-react-app\n' +
'- yarn global remove create-react-app'
);
console.log();
console.log(
'The latest instructions for creating a new app can be found here:\n' +
'https://create-react-app.dev/docs/getting-started/'
);
console.log();
process.exit(1);
} else {
createApp(
projectName,
program.verbose,
program.scriptsVersion,
program.template,
program.useNpm,
program.usePnp
);
}
});
}

// We first check the registry directly via the API, and if that fails, we try
// the slower `npm view [package] version` command.
//
// This is important for users in environments where direct access to npm is
// blocked by a firewall, and packages are provided exclusively via a private
// registry.
checkForLatestVersion()
.catch(() => {
try {
return execSync('npm view create-react-app version').toString().trim();
} catch (e) {
return null;
}
})
.then(latest => {
if (latest && semver.lt(packageJson.version, latest)) {
console.log();
console.error(
chalk.yellow(
`You are running \`create-react-app\` ${packageJson.version}, which is behind the latest release (${latest}).\n\n` +
'We no longer support global installation of Create React App.'
)
);
console.log();
console.log(
'Please remove any global installs with one of the following commands:\n' +
'- npm uninstall -g create-react-app\n' +
'- yarn global remove create-react-app'
);
console.log();
console.log(
'The latest instructions for creating a new app can be found here:\n' +
'https://create-react-app.dev/docs/getting-started/'
);
console.log();
process.exit(1);
} else {
createApp(
projectName,
program.verbose,
program.scriptsVersion,
program.template,
program.useNpm,
program.usePnp
);
}
});

function createApp(name, verbose, version, template, useNpm, usePnp) {
const unsupportedNodeVersion = !semver.satisfies(process.version, '>=10');
if (unsupportedNodeVersion) {
@@ -628,10 +640,11 @@ function getTemplateInstallPackage(template, originalDirectory) {
templateToInstall = template;
} else {
// Add prefix 'cra-template-' to non-prefixed templates, leaving any
// @scope/ intact.
const packageMatch = template.match(/^(@[^/]+\/)?(.+)$/);
// @scope/ and @version intact.
const packageMatch = template.match(/^(@[^/]+\/)?([^@]+)?(@.+)?$/);
const scope = packageMatch[1] || '';
const templateName = packageMatch[2];
const templateName = packageMatch[2] || '';
const version = packageMatch[3] || '';

if (
templateName === templateToInstall ||
@@ -642,15 +655,15 @@ function getTemplateInstallPackage(template, originalDirectory) {
// - @SCOPE/cra-template
// - cra-template-NAME
// - @SCOPE/cra-template-NAME
templateToInstall = `${scope}${templateName}`;
} else if (templateName.startsWith('@')) {
templateToInstall = `${scope}${templateName}${version}`;
} else if (version && !scope && !templateName) {
// Covers using @SCOPE only
templateToInstall = `${templateName}/${templateToInstall}`;
templateToInstall = `${version}/${templateToInstall}`;
} else {
// Covers templates without the `cra-template` prefix:
// - NAME
// - @SCOPE/NAME
templateToInstall = `${scope}${templateToInstall}-${templateName}`;
templateToInstall = `${scope}${templateToInstall}-${templateName}${version}`;
}
}
}
@@ -1133,3 +1146,8 @@ function checkForLatestVersion() {
});
});
}

module.exports = {
init,
getTemplateInstallPackage,
};
4 changes: 3 additions & 1 deletion packages/create-react-app/index.js
Original file line number Diff line number Diff line change
@@ -51,4 +51,6 @@ if (major < 10) {
process.exit(1);
}

require('./createReactApp');
const { init } = require('./createReactApp');

init();
7 changes: 7 additions & 0 deletions packages/create-react-app/package.json
Original file line number Diff line number Diff line change
@@ -25,6 +25,9 @@
"bin": {
"create-react-app": "./index.js"
},
"scripts": {
"test": "cross-env FORCE_COLOR=true jest"
},
"dependencies": {
"chalk": "4.1.0",
"commander": "4.1.0",
@@ -37,5 +40,9 @@
"tar-pack": "3.4.1",
"tmp": "0.2.1",
"validate-npm-package-name": "3.0.0"
},
"devDependencies": {
"cross-env": "^7.0.2",
"jest": "26.1.0"
}
}