Skip to content

Commit

Permalink
Properly extract package name for installing tgz of scoped packages (#…
Browse files Browse the repository at this point in the history
…1706)

* Properly extract package name

* Download package if need be ...

* Oops

* Add e2e test based on #1537, but without specific filename

* Pass packageName through promises

A little bit more verbose but explicit and doesn't rely on shared mutable state.

* Fix up directory name in test

* Tweak failure message

* Fix lint
  • Loading branch information
Timer authored and gaearon committed Mar 5, 2017
1 parent 1a331e1 commit 96fe3fc
Show file tree
Hide file tree
Showing 3 changed files with 104 additions and 20 deletions.
109 changes: 89 additions & 20 deletions packages/create-react-app/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@ var execSync = require('child_process').execSync;
var spawn = require('cross-spawn');
var semver = require('semver');
var dns = require('dns');
var tmp = require('tmp');
var unpack = require('tar-pack').unpack;
var hyperquest = require('hyperquest');

var projectName;

Expand Down Expand Up @@ -201,23 +204,34 @@ function install(useYarn, dependencies, verbose, isOnline) {

function run(root, appName, version, verbose, originalDirectory, template) {
var packageToInstall = getInstallPackage(version);
var packageName = getPackageName(packageToInstall);

var allDependencies = ['react', 'react-dom', packageToInstall];

console.log('Installing packages. This might take a couple minutes.');
console.log(
'Installing ' + chalk.cyan('react') + ', ' + chalk.cyan('react-dom') +
', and ' + chalk.cyan(packageName) + '...'
);
console.log();


var useYarn = shouldUseYarn();
checkIfOnline(useYarn)
.then(function(isOnline) {
return install(useYarn, allDependencies, verbose, isOnline);
getPackageName(packageToInstall)
.then(function(packageName) {
return checkIfOnline(useYarn).then(function(isOnline) {
return {
isOnline: isOnline,
packageName: packageName,
};
});
})
.then(function(info) {
var isOnline = info.isOnline;
var packageName = info.packageName;
console.log(
'Installing ' + chalk.cyan('react') + ', ' + chalk.cyan('react-dom') +
', and ' + chalk.cyan(packageName) + '...'
);
console.log();

return install(useYarn, allDependencies, verbose, isOnline).then(function() {
return packageName;
});
})
.then(function() {
.then(function(packageName) {
checkNodeVersion(packageName);

// Since react-scripts has been installed with --save
Expand Down Expand Up @@ -285,22 +299,77 @@ function getInstallPackage(version) {
return packageToInstall;
}

function getTemporaryDirectory() {
return new Promise(function(resolve, reject) {
// Unsafe cleanup lets us recursively delete the directory if it contains
// contents; by default it only allows removal if it's empty
tmp.dir({ unsafeCleanup: true }, function(err, tmpdir, callback) {
if (err) {
reject(err);
} else {
resolve({
tmpdir: tmpdir,
cleanup: function() {
try {
callback();
} catch (ignored) {
// Callback might throw and fail, since it's a temp directory the
// OS will clean it up eventually...
}
}
});
}
});
});
}

function extractStream(stream, dest) {
return new Promise(function(resolve, reject) {
stream.pipe(unpack(dest, function(err) {
if (err) {
reject(err);
} else {
resolve(dest);
}
}));
});
}

// Extract package name from tarball url or path.
function getPackageName(installPackage) {
if (installPackage.indexOf('.tgz') > -1) {
// The package name could be with or without semver version, e.g. react-scripts-0.2.0-alpha.1.tgz
// However, this function returns package name only without semver version.
return installPackage.match(/^.+\/(.+?)(?:-\d+.+)?\.tgz$/)[1];
return getTemporaryDirectory().then(function(obj) {
var stream;
if (/^http/.test(installPackage)) {
stream = hyperquest(installPackage);
} else {
stream = fs.createReadStream(installPackage);
}
return extractStream(stream, obj.tmpdir).then(function() {
return obj;
});
}).then(function(obj) {
var packageName = require(path.join(obj.tmpdir, 'package.json')).name;
obj.cleanup();
return packageName;
}).catch(function(err) {
// The package name could be with or without semver version, e.g. react-scripts-0.2.0-alpha.1.tgz
// However, this function returns package name only without semver version.
console.log('Could not extract the package name from the archive: ' + err.message);
var assumedProjectName = installPackage.match(/^.+\/(.+?)(?:-\d+.+)?\.tgz$/)[1];
console.log('Based on the filename, assuming it is "' + chalk.cyan(assumedProjectName) + '"');
return Promise.resolve(assumedProjectName);
});
} else if (installPackage.indexOf('git+') === 0) {
// Pull package name out of git urls e.g:
// git+https://github.com/mycompany/react-scripts.git
// git+ssh://github.com/mycompany/react-scripts.git#v1.2.3
return installPackage.match(/([^\/]+)\.git(#.*)?$/)[1];
return Promise.resolve(installPackage.match(/([^\/]+)\.git(#.*)?$/)[1]);
} else if (installPackage.indexOf('@') > 0) {
// Do not match @scope/ when stripping off @version or @tag
return installPackage.charAt(0) + installPackage.substr(1).split('@')[0];
return Promise.resolve(installPackage.charAt(0) + installPackage.substr(1).split('@')[0]);
}
return installPackage;
return Promise.resolve(installPackage);
}

function checkNpmVersion() {
Expand Down Expand Up @@ -356,7 +425,7 @@ function checkAppName(appName) {
printValidationResults(validationResult.warnings);
process.exit(1);
}

// TODO: there should be a single place that holds the dependencies
var dependencies = ['react', 'react-dom'];
var devDependencies = ['react-scripts'];
Expand Down Expand Up @@ -449,7 +518,7 @@ function checkIfOnline(useYarn) {
// We'll just assume the best case.
return Promise.resolve(true);
}

return new Promise(function(resolve) {
dns.resolve('registry.yarnpkg.com', function(err) {
resolve(err === null);
Expand Down
3 changes: 3 additions & 0 deletions packages/create-react-app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,10 @@
"commander": "^2.9.0",
"cross-spawn": "^4.0.0",
"fs-extra": "^1.0.0",
"hyperquest": "^2.1.2",
"semver": "^5.0.3",
"tar-pack": "^3.4.0",
"tmp": "0.0.31",
"validate-npm-package-name": "^3.0.0"
}
}
12 changes: 12 additions & 0 deletions tasks/e2e-installs.sh
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,18 @@ if [ "$(ls -1 ./test-app-should-remain | wc -l | tr -d '[:space:]')" != "1" ]; t
false
fi

# ******************************************************************************
# Test --scripts-version with a scoped fork tgz of react-scripts
# ******************************************************************************

cd $temp_app_path
curl "https://registry.npmjs.org/@enoah_netzach/react-scripts/-/react-scripts-0.9.0.tgz" -o enoah-scripts-0.9.0.tgz
create_react_app --scripts-version=$temp_app_path/enoah-scripts-0.9.0.tgz test-app-scoped-fork-tgz
cd test-app-scoped-fork-tgz

# Check corresponding scripts version is installed.
exists node_modules/@enoah_netzach/react-scripts

# ******************************************************************************
# Test nested folder path as the project name
# ******************************************************************************
Expand Down

0 comments on commit 96fe3fc

Please sign in to comment.