diff --git a/package.json b/package.json index 67c16bbfd09..61d4ffd2d3a 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,9 @@ "branch": "", "SHA": "" }, + "dependencies": { + "npm": "2.15.10" + }, "devDependencies": { "grunt": "0.4.5", "jasmine-node": "1.11.0", diff --git a/src/extensibility/node/ExtensionManagerDomain.js b/src/extensibility/node/ExtensionManagerDomain.js index 0fbc6357ffe..473eeea5f87 100644 --- a/src/extensibility/node/ExtensionManagerDomain.js +++ b/src/extensibility/node/ExtensionManagerDomain.js @@ -30,6 +30,7 @@ var semver = require("semver"), request = require("request"), fs = require("fs-extra"), temp = require("temp"), + npm = require("npm"), validate = require("./package-validator").validate; // Automatically clean up temp files on exit @@ -97,17 +98,61 @@ function _performInstall(packagePath, installDirectory, validationResult, callba var sourceDir = path.join(validationResult.extractDir, validationResult.commonPrefix); fs.copy(sourceDir, installDirectory, function (err) { - if (err) { + + var fail = function (err) { _removeFailedInstallation(installDirectory); callback(err, null); - } else { + }; + + var finish = function () { // The status may have already been set previously (as in the // DISABLED case. if (!validationResult.installationStatus) { validationResult.installationStatus = Statuses.INSTALLED; } callback(null, validationResult); + }; + + if (err) { + fail(err); + return; + } + + var packageJson, + npmDependencies; + + try { + packageJson = JSON.parse(fs.readFileSync(path.join(installDirectory, "package.json"), { + encoding: "utf8" + })); + } catch (e) { + packageJson = null; } + + if (packageJson && packageJson.dependencies) { + npmDependencies = Object.keys(packageJson.dependencies).map(function (key) { + return key + "@" + packageJson.dependencies[key]; + }); + } + + if (npmDependencies && npmDependencies.length > 0) { + npm.load(function (err, npm) { + if (err) { + fail(err); + return; + } + npm.commands.install(installDirectory, npmDependencies, function (err, result) { + if (err) { + fail(err); + return; + } + finish(); + }); + }); + } else { + finish(); + } + }); }); } diff --git a/src/extensibility/node/spec/Installation.spec.js b/src/extensibility/node/spec/Installation.spec.js index e4222314811..58756c099b7 100644 --- a/src/extensibility/node/spec/Installation.spec.js +++ b/src/extensibility/node/spec/Installation.spec.js @@ -54,7 +54,8 @@ var basicValidExtension = path.join(testFilesDirectory, "basic-valid-exten missingPackageJSON = path.join(testFilesDirectory, "missing-package-json.zip"), missingPackageJSONUpdate = path.join(testFilesDirectory, "missing-package-json-update.zip"), missingPackageJSONRenamed = path.join(testFilesDirectory, "added-package-json-test", "missing-package-json.zip"), - withSymlink = path.join(testFilesDirectory, "with-symlink.zip"); + withSymlink = path.join(testFilesDirectory, "with-symlink.zip"), + withNpmDependencies = path.join(testFilesDirectory, "with-npm-dependencies.zip"); describe("Package Installation", function () { @@ -323,4 +324,30 @@ describe("Package Installation", function () { done(); }); }); + + it("should download npm dependencies when present", function (done) { + ExtensionsDomain._cmdInstall(withNpmDependencies, installDirectory, standardOptions, function (err, result) { + expect(err).toBeNull(); + expect(result.errors.length).toEqual(0); + expect(fs.existsSync(result.installedTo)).toBe(true); + expect(fs.existsSync(path.join(result.installedTo, "node_modules"))).toBe(true); + + expect(fs.existsSync(path.join(result.installedTo, "node_modules", "lodash"))).toBe(true); + expect(fs.existsSync(path.join(result.installedTo, "node_modules", "lodash", "package.json"))).toBe(true); + var packageInfo = JSON.parse(fs.readFileSync(path.join(result.installedTo, "node_modules", "lodash", "package.json"))); + expect(packageInfo.version.slice(0,2)).toBe("3."); + + expect(fs.existsSync(path.join(result.installedTo, "node_modules", "moment"))).toBe(true); + expect(fs.existsSync(path.join(result.installedTo, "node_modules", "moment", "package.json"))).toBe(true); + packageInfo = JSON.parse(fs.readFileSync(path.join(result.installedTo, "node_modules", "moment", "package.json"))); + expect(packageInfo.version.slice(0,4)).toBe("2.5."); + + expect(fs.existsSync(path.join(result.installedTo, "node_modules", "underscore"))).toBe(true); + expect(fs.existsSync(path.join(result.installedTo, "node_modules", "underscore", "package.json"))).toBe(true); + packageInfo = JSON.parse(fs.readFileSync(path.join(result.installedTo, "node_modules", "underscore", "package.json"))); + expect(packageInfo.version).toBe("1.0.4"); + + done(); + }); + }); }); diff --git a/test/spec/extension-test-files/with-npm-dependencies.zip b/test/spec/extension-test-files/with-npm-dependencies.zip new file mode 100644 index 00000000000..4da73059264 Binary files /dev/null and b/test/spec/extension-test-files/with-npm-dependencies.zip differ