diff --git a/lib/base-package-manager.ts b/lib/base-package-manager.ts new file mode 100644 index 0000000000..b53ee9dcb7 --- /dev/null +++ b/lib/base-package-manager.ts @@ -0,0 +1,46 @@ +import { isInteractive } from "./common/helpers"; + +export class BasePackageManager { + constructor( + protected $childProcess: IChildProcess, + private $hostInfo: IHostInfo, + private packageManager: string + ) { } + + protected getPackageManagerExecutableName(): string { + let npmExecutableName = this.packageManager; + + if (this.$hostInfo.isWindows) { + npmExecutableName += ".cmd"; + } + + return npmExecutableName; + } + + protected async processPackageManagerInstall(params: string[], opts: { cwd: string }) { + const npmExecutable = this.getPackageManagerExecutableName(); + const stdioValue = isInteractive() ? "inherit" : "pipe"; + return await this.$childProcess.spawnFromEvent(npmExecutable, params, "close", { cwd: opts.cwd, stdio: stdioValue }); + } + + protected getFlagsString(config: any, asArray: boolean): any { + const array: Array = []; + for (const flag in config) { + if (flag === "global" && this.packageManager !== 'yarn') { + array.push(`--${flag}`); + array.push(`${config[flag]}`); + } else if (config[flag]) { + if (flag === "dist-tags" || flag === "versions") { + array.push(` ${flag}`); + continue; + } + array.push(`--${flag}`); + } + } + if (asArray) { + return array; + } + + return array.join(" "); + } +} diff --git a/lib/bootstrap.ts b/lib/bootstrap.ts index 770a938183..590d72a1e6 100644 --- a/lib/bootstrap.ts +++ b/lib/bootstrap.ts @@ -79,8 +79,12 @@ $injector.require("itmsTransporterService", "./services/itmstransporter-service" $injector.requireCommand("setup|*", "./commands/setup"); $injector.requireCommand(["setup|cloud", "cloud|setup"], "./commands/setup"); +$injector.requirePublic("packageManager", "./package-manager"); $injector.requirePublic("npm", "./node-package-manager"); -$injector.require("npmInstallationManager", "./npm-installation-manager"); +$injector.requirePublic("yarn", "./yarn-package-manager"); +$injector.requireCommand("package-manager|set", "./commands/package-manager-set"); + +$injector.require("packageInstallationManager", "./package-installation-manager"); $injector.require("dynamicHelpProvider", "./dynamic-help-provider"); $injector.require("mobilePlatformsCapabilities", "./mobile-platforms-capabilities"); $injector.require("commandsServiceProvider", "./providers/commands-service-provider"); diff --git a/lib/commands/install.ts b/lib/commands/install.ts index 751b2ecfd6..a92ee7a6ab 100644 --- a/lib/commands/install.ts +++ b/lib/commands/install.ts @@ -13,7 +13,7 @@ export class InstallCommand implements ICommand { private $logger: ILogger, private $fs: IFileSystem, private $stringParameter: ICommandParameter, - private $npm: INodePackageManager) { + private $packageManager: INodePackageManager) { this.$projectData.initializeProjectData(); } @@ -54,7 +54,7 @@ export class InstallCommand implements ICommand { moduleName = devPrefix + moduleName; } - await this.$npm.install(moduleName, projectDir, { + await this.$packageManager.install(moduleName, projectDir, { 'save-dev': true, disableNpmInstall: this.$options.disableNpmInstall, frameworkPath: this.$options.frameworkPath, diff --git a/lib/commands/plugin/create-plugin.ts b/lib/commands/plugin/create-plugin.ts index 423f0c815d..847a3ff1f3 100644 --- a/lib/commands/plugin/create-plugin.ts +++ b/lib/commands/plugin/create-plugin.ts @@ -13,7 +13,7 @@ export class CreatePluginCommand implements ICommand { private $fs: IFileSystem, private $childProcess: IChildProcess, private $prompter: IPrompter, - private $npm: INodePackageManager) { } + private $packageManager: INodePackageManager) { } public async execute(args: string[]): Promise { const pluginRepoName = args[0]; @@ -45,7 +45,7 @@ export class CreatePluginCommand implements ICommand { try { spinner.start(); const npmOptions: any = { silent: true }; - await this.$npm.install(cwd, cwd, npmOptions); + await this.$packageManager.install(cwd, cwd, npmOptions); } finally { spinner.stop(); } diff --git a/lib/commands/test-init.ts b/lib/commands/test-init.ts index 10099c9461..e855d54fb8 100644 --- a/lib/commands/test-init.ts +++ b/lib/commands/test-init.ts @@ -12,7 +12,7 @@ class TestInitCommand implements ICommand { mocha: ['chai'] }; - constructor(private $npm: INodePackageManager, + constructor(private $packageManager: INodePackageManager, private $projectData: IProjectData, private $errors: IErrors, private $options: IOptions, @@ -55,7 +55,7 @@ class TestInitCommand implements ICommand { if (mod.version) { moduleToInstall += `@${mod.version}`; } - await this.$npm.install(moduleToInstall, projectDir, { + await this.$packageManager.install(moduleToInstall, projectDir, { 'save-dev': true, 'save-exact': true, optional: false, @@ -76,7 +76,7 @@ class TestInitCommand implements ICommand { // catch errors when a peerDependency is already installed // e.g karma is installed; karma-jasmine depends on karma and will try to install it again try { - await this.$npm.install(`${peerDependency}@${dependencyVersion}`, projectDir, { + await this.$packageManager.install(`${peerDependency}@${dependencyVersion}`, projectDir, { 'save-dev': true, 'save-exact': true, disableNpmInstall: false, diff --git a/lib/common/commands/package-manager-set.ts b/lib/common/commands/package-manager-set.ts new file mode 100644 index 0000000000..365dc8069e --- /dev/null +++ b/lib/common/commands/package-manager-set.ts @@ -0,0 +1,20 @@ + +export class PackageManagerCommand implements ICommand { + + constructor(private $userSettingsService: IUserSettingsService, + private $errors: IErrors, + private $stringParameter: ICommandParameter) { } + + public allowedParameters: ICommandParameter[] = [this.$stringParameter]; + + public execute(args: string[]): Promise { + if (args[0] === 'yarn' ) { + return this.$userSettingsService.saveSetting("packageManager", "yarn"); + } else if ( args[0] === 'npm') { + return this.$userSettingsService.saveSetting("packageManager", "npm"); + } + return this.$errors.fail(`${args[0]} is not a valid package manager. Only yarn or npm are supported.`); + } +} + +$injector.registerCommand("package-manager|set", PackageManagerCommand); diff --git a/lib/declarations.d.ts b/lib/declarations.d.ts index ef54b193b8..175fa8ec4b 100644 --- a/lib/declarations.d.ts +++ b/lib/declarations.d.ts @@ -54,7 +54,7 @@ interface INodePackageManager { getCachePath(): Promise; } -interface INpmInstallationManager { +interface IPackageInstallationManager { install(packageName: string, packageDir: string, options?: INpmInstallOptions): Promise; getLatestVersion(packageName: string): Promise; getNextVersion(packageName: string): Promise; @@ -506,6 +506,7 @@ interface IOptions extends IRelease, IDeviceIdentifier, IJustLaunch, IAvd, IAvai framework: string; frameworkName: string; frameworkVersion: string; + yarn: string, ipa: string; tsc: boolean; ts: boolean; diff --git a/lib/node-package-manager.ts b/lib/node-package-manager.ts index 1a82ac4cae..c3af8dcb08 100644 --- a/lib/node-package-manager.ts +++ b/lib/node-package-manager.ts @@ -1,18 +1,21 @@ import * as path from "path"; +import { BasePackageManager } from "./base-package-manager"; import { exported, cache } from "./common/decorators"; -import { isInteractive } from "./common/helpers"; import { CACACHE_DIRECTORY_NAME } from "./constants"; -export class NodePackageManager implements INodePackageManager { +export class NodePackageManager extends BasePackageManager implements INodePackageManager { private static SCOPED_DEPENDENCY_REGEXP = /^(@.+?)(?:@(.+?))?$/; private static DEPENDENCY_REGEXP = /^(.+?)(?:@(.+?))?$/; - constructor(private $fs: IFileSystem, - private $hostInfo: IHostInfo, + constructor( + $childProcess: IChildProcess, private $errors: IErrors, - private $childProcess: IChildProcess, + private $fs: IFileSystem, + $hostInfo: IHostInfo, private $logger: ILogger, - private $httpClient: Server.IHttpClient) { } + private $httpClient: Server.IHttpClient) { + super($childProcess, $hostInfo, 'npm'); + } @exported("npm") public async install(packageName: string, pathToSave: string, config: INodePackageManagerInstallOptions): Promise { @@ -53,7 +56,7 @@ export class NodePackageManager implements INodePackageManager { } try { - const spawnResult: ISpawnResult = await this.getNpmInstallResult(params, cwd); + const spawnResult: ISpawnResult = await this.processPackageManagerInstall(params, { cwd }); // Whenever calling npm install without any arguments (hence installing all dependencies) no output is emitted on stdout // Luckily, whenever you call npm install to install all dependencies chances are you won't need the name/version of the package you're installing because there is none. @@ -66,7 +69,7 @@ export class NodePackageManager implements INodePackageManager { // We cannot use the actual install with --json to get the information because of post-install scripts which may print on stdout // dry-run install is quite fast when the dependencies are already installed even for many dependencies (e.g. angular) so we can live with this approach // We need the --prefix here because without it no output is emitted on stdout because all the dependencies are already installed. - const spawnNpmDryRunResult = await this.$childProcess.spawnFromEvent(this.getNpmExecutableName(), params, "close"); + const spawnNpmDryRunResult = await this.$childProcess.spawnFromEvent(this.getPackageManagerExecutableName(), params, "close"); return this.parseNpmInstallResult(spawnNpmDryRunResult.stdout, spawnResult.stdout, packageName); } catch (err) { if (err.message && err.message.indexOf("EPEERINVALID") !== -1) { @@ -136,43 +139,12 @@ export class NodePackageManager implements INodePackageManager { return path.join(cachePath.trim(), CACACHE_DIRECTORY_NAME); } - private getNpmExecutableName(): string { - let npmExecutableName = "npm"; - - if (this.$hostInfo.isWindows) { - npmExecutableName += ".cmd"; - } - - return npmExecutableName; - } - - private getFlagsString(config: any, asArray: boolean): any { - const array: Array = []; - for (const flag in config) { - if (flag === "global") { - array.push(`--${flag}`); - array.push(`${config[flag]}`); - } else if (config[flag]) { - if (flag === "dist-tags" || flag === "versions") { - array.push(` ${flag}`); - continue; - } - array.push(`--${flag}`); - } - } - if (asArray) { - return array; - } - - return array.join(" "); - } - private parseNpmInstallResult(npmDryRunInstallOutput: string, npmInstallOutput: string, userSpecifiedPackageName: string): INpmInstallResultInfo { // TODO: Add tests for this functionality try { const originalOutput: INpmInstallCLIResult | INpm5InstallCliResult = JSON.parse(npmDryRunInstallOutput); - const npm5Output = originalOutput; - const npmOutput = originalOutput; + const npm5Output = originalOutput; + const npmOutput = originalOutput; let name: string; _.forOwn(npmOutput.dependencies, (peerDependency: INpmPeerDependencyInfo, key: string) => { if (!peerDependency.required && !peerDependency.peerMissing) { @@ -239,64 +211,6 @@ export class NodePackageManager implements INodePackageManager { version }; } - - private async getNpmInstallResult(params: string[], cwd: string): Promise { - return new Promise((resolve, reject) => { - const npmExecutable = this.getNpmExecutableName(); - const stdioValue = isInteractive() ? "inherit" : "pipe"; - - const childProcess = this.$childProcess.spawn(npmExecutable, params, { cwd, stdio: stdioValue }); - - let isFulfilled = false; - let capturedOut = ""; - let capturedErr = ""; - - if (childProcess.stdout) { - childProcess.stdout.on("data", (data: string) => { - this.$logger.write(data.toString()); - capturedOut += data; - }); - } - - if (childProcess.stderr) { - childProcess.stderr.on("data", (data: string) => { - capturedErr += data; - }); - } - - childProcess.on("close", (arg: any) => { - const exitCode = typeof arg === "number" ? arg : arg && arg.code; - - if (exitCode === 0) { - isFulfilled = true; - const result = { - stdout: capturedOut, - stderr: capturedErr, - exitCode - }; - - resolve(result); - } else { - let errorMessage = `Command ${npmExecutable} ${params && params.join(" ")} failed with exit code ${exitCode}`; - if (capturedErr) { - errorMessage += ` Error output: \n ${capturedErr}`; - } - - if (!isFulfilled) { - isFulfilled = true; - reject(new Error(errorMessage)); - } - } - }); - - childProcess.on("error", (err: Error) => { - if (!isFulfilled) { - isFulfilled = true; - reject(err); - } - }); - }); - } } $injector.register("npm", NodePackageManager); diff --git a/lib/options.ts b/lib/options.ts index c20c7c4b58..20d428cade 100644 --- a/lib/options.ts +++ b/lib/options.ts @@ -71,6 +71,7 @@ export class Options { tsc: { type: OptionType.Boolean }, ts: { type: OptionType.Boolean }, typescript: { type: OptionType.Boolean }, + yarn: { type: OptionType.Boolean }, androidTypings: { type: OptionType.Boolean }, bundle: { type: OptionType.String }, all: { type: OptionType.Boolean }, diff --git a/lib/npm-installation-manager.ts b/lib/package-installation-manager.ts similarity index 92% rename from lib/npm-installation-manager.ts rename to lib/package-installation-manager.ts index b3501286ca..15ba2a9249 100644 --- a/lib/npm-installation-manager.ts +++ b/lib/package-installation-manager.ts @@ -2,8 +2,9 @@ import * as path from "path"; import * as semver from "semver"; import * as constants from "./constants"; -export class NpmInstallationManager implements INpmInstallationManager { - constructor(private $npm: INodePackageManager, +export class PackageInstallationManager implements IPackageInstallationManager { + constructor( + private $packageManager: INodePackageManager, private $childProcess: IChildProcess, private $logger: ILogger, private $settingsService: ISettingsService, @@ -32,7 +33,7 @@ export class NpmInstallationManager implements INpmInstallationManager { return latestVersion; } - const data = await this.$npm.view(packageName, { "versions": true }); + const data = await this.$packageManager.view(packageName, { "versions": true }); const maxSatisfying = semver.maxSatisfying(data, compatibleVersionRange); return maxSatisfying || latestVersion; @@ -144,7 +145,7 @@ export class NpmInstallationManager implements INpmInstallationManager { npmOptions[dependencyType] = true; } - return await this.$npm.install(packageName, pathToSave, npmOptions); + return await this.$packageManager.install(packageName, pathToSave, npmOptions); } /** @@ -152,10 +153,10 @@ export class NpmInstallationManager implements INpmInstallationManager { * because npm view doens't work with those */ private async getVersion(packageName: string, version: string): Promise { - const data: any = await this.$npm.view(packageName, { "dist-tags": true }); + const data: any = await this.$packageManager.view(packageName, { "dist-tags": true }); this.$logger.trace("Using version %s. ", data[version]); return data[version]; } } -$injector.register("npmInstallationManager", NpmInstallationManager); +$injector.register("packageInstallationManager", PackageInstallationManager); diff --git a/lib/package-manager.ts b/lib/package-manager.ts new file mode 100644 index 0000000000..6f36883c18 --- /dev/null +++ b/lib/package-manager.ts @@ -0,0 +1,71 @@ + +import { cache, exported, invokeInit } from './common/decorators'; +export class PackageManager implements INodePackageManager { + private packageManager: INodePackageManager; + + constructor( + private $errors: IErrors, + private $npm: INodePackageManager, + private $options: IOptions, + private $yarn: INodePackageManager, + private $userSettingsService: IUserSettingsService + ) {} + + @cache() + protected async init(): Promise { + this.packageManager = await this._determinePackageManager(); + } + + @exported("packageManager") + @invokeInit() + public install(packageName: string, pathToSave: string, config: INodePackageManagerInstallOptions): Promise { + return this.packageManager.install(packageName, pathToSave, config); + } + @exported("packageManager") + @invokeInit() + public uninstall(packageName: string, config?: IDictionary, path?: string): Promise { + return this.packageManager.uninstall(packageName, config, path); + } + @exported("packageManager") + @invokeInit() + public view(packageName: string, config: Object): Promise { + return this.packageManager.view(packageName, config); + } + @exported("packageManager") + @invokeInit() + public search(filter: string[], config: IDictionary): Promise { + return this.packageManager.search(filter, config); + } + + @invokeInit() + public searchNpms(keyword: string): Promise { + return this.packageManager.searchNpms(keyword); + } + + @invokeInit() + public getRegistryPackageData(packageName: string): Promise { + return this.packageManager.getRegistryPackageData(packageName); + } + + @invokeInit() + public getCachePath(): Promise { + return this.packageManager.getCachePath(); + } + + private async _determinePackageManager(): Promise { + let pm = null; + try { + pm = await this.$userSettingsService.getSettingValue('packageManager'); + } catch (err) { + this.$errors.fail(`Unable to read package manager config from user settings ${err}`); + } + + if (pm === 'yarn' || this.$options.yarn) { + return this.$yarn; + } else { + return this.$npm; + } + } +} + +$injector.register('packageManager', PackageManager); diff --git a/lib/services/android-plugin-build-service.ts b/lib/services/android-plugin-build-service.ts index 91639a46c2..d19e50f344 100644 --- a/lib/services/android-plugin-build-service.ts +++ b/lib/services/android-plugin-build-service.ts @@ -22,7 +22,7 @@ export class AndroidPluginBuildService implements IAndroidPluginBuildService { private $hostInfo: IHostInfo, private $androidToolsInfo: IAndroidToolsInfo, private $logger: ILogger, - private $npm: INodePackageManager, + private $packageManager: INodePackageManager, private $projectDataService: IProjectDataService, private $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, private $errors: IErrors, @@ -295,7 +295,7 @@ export class AndroidPluginBuildService implements IAndroidPluginBuildService { } private async getRuntimeGradleVersions(projectDir: string): Promise { - const registryData = await this.$npm.getRegistryPackageData(TNS_ANDROID_RUNTIME_NAME); + const registryData = await this.$packageManager.getRegistryPackageData(TNS_ANDROID_RUNTIME_NAME); let runtimeGradleVersions: IRuntimeGradleVersions = null; if (projectDir) { const projectRuntimeVersion = this.$platformService.getCurrentPlatformVersion( diff --git a/lib/services/android-project-service.ts b/lib/services/android-project-service.ts index 6b955802df..f03926a4e4 100644 --- a/lib/services/android-project-service.ts +++ b/lib/services/android-project-service.ts @@ -27,7 +27,7 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject private $injector: IInjector, private $pluginVariablesService: IPluginVariablesService, private $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, - private $npm: INodePackageManager, + private $packageManager: INodePackageManager, private $androidPluginBuildService: IAndroidPluginBuildService, private $platformEnvironmentRequirements: IPlatformEnvironmentRequirements, private $androidResourcesMigrationService: IAndroidResourcesMigrationService, @@ -206,7 +206,7 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject (projectPackageJson.devDependencies && projectPackageJson.devDependencies[dependency.name]); if (!dependencyVersionInProject) { - await this.$npm.install(`${dependency.name}@${dependency.version}`, projectData.projectDir, npmConfig); + await this.$packageManager.install(`${dependency.name}@${dependency.version}`, projectData.projectDir, npmConfig); } else { const cleanedVersion = semver.clean(dependencyVersionInProject); diff --git a/lib/services/extensibility-service.ts b/lib/services/extensibility-service.ts index d2f902ceef..fa190dfbd7 100644 --- a/lib/services/extensibility-service.ts +++ b/lib/services/extensibility-service.ts @@ -14,7 +14,7 @@ export class ExtensibilityService implements IExtensibilityService { constructor(private $fs: IFileSystem, private $logger: ILogger, - private $npm: INodePackageManager, + private $packageManager: INodePackageManager, private $settingsService: ISettingsService, private $requireService: IRequireService) { } @@ -33,7 +33,7 @@ export class ExtensibilityService implements IExtensibilityService { const localPath = path.resolve(extensionName); const packageName = this.$fs.exists(localPath) ? localPath : extensionName; - const installResultInfo = await this.$npm.install(packageName, this.pathToExtensions, npmOpts); + const installResultInfo = await this.$packageManager.install(packageName, this.pathToExtensions, npmOpts); this.$logger.trace(`Finished installation of extension '${extensionName}'. Trying to load it now.`); return this.getInstalledExtensionData(installResultInfo.name); @@ -45,7 +45,7 @@ export class ExtensibilityService implements IExtensibilityService { await this.assertPackageJsonExists(); - await this.$npm.uninstall(extensionName, { save: true }, this.pathToExtensions); + await this.$packageManager.uninstall(extensionName, { save: true }, this.pathToExtensions); this.$logger.trace(`Finished uninstallation of extension '${extensionName}'.`); } @@ -112,7 +112,7 @@ export class ExtensibilityService implements IExtensibilityService { let allExtensions: INpmsSingleResultData[] = []; try { - const npmsResult = await this.$npm.searchNpms("nativescript:extension"); + const npmsResult = await this.$packageManager.searchNpms("nativescript:extension"); allExtensions = npmsResult.results || []; } catch (err) { this.$logger.trace(`Unable to find extensions via npms. Error is: ${err}`); @@ -127,7 +127,7 @@ export class ExtensibilityService implements IExtensibilityService { try { // now get full package.json for the latest version of the package - const registryData = await this.$npm.getRegistryPackageData(extensionName); + const registryData = await this.$packageManager.getRegistryPackageData(extensionName); const latestPackageData = registryData.versions[registryData["dist-tags"].latest]; const commands: string[] = latestPackageData && latestPackageData.nativescript && latestPackageData.nativescript.commands; if (commands && commands.length) { diff --git a/lib/services/init-service.ts b/lib/services/init-service.ts index 4017d29863..6f9f3dfca4 100644 --- a/lib/services/init-service.ts +++ b/lib/services/init-service.ts @@ -21,8 +21,8 @@ export class InitService implements IInitService { private $staticConfig: IStaticConfig, private $projectHelper: IProjectHelper, private $prompter: IPrompter, - private $npm: INodePackageManager, - private $npmInstallationManager: INpmInstallationManager) { } + private $packageManager: INodePackageManager, + private $packageInstallationManager: IPackageInstallationManager) { } public async initialize(): Promise { let projectData: any = {}; @@ -100,13 +100,13 @@ export class InitService implements IInitService { } private async getVersionData(packageName: string): Promise { - const latestVersion = await this.$npmInstallationManager.getLatestCompatibleVersion(packageName); + const latestVersion = await this.$packageInstallationManager.getLatestCompatibleVersion(packageName); if (this.useDefaultValue) { return this.buildVersionData(latestVersion); } - const allVersions: any = await this.$npm.view(packageName, { "versions": true }); + const allVersions: any = await this.$packageManager.view(packageName, { "versions": true }); const versions = _.filter(allVersions, (version: string) => semver.gte(version, InitService.MIN_SUPPORTED_FRAMEWORK_VERSIONS[packageName])); if (versions.length === 1) { this.$logger.info(`Only ${versions[0]} version is available for ${packageName}.`); diff --git a/lib/services/ios-debug-service.ts b/lib/services/ios-debug-service.ts index 94648ecc54..040b4c4e69 100644 --- a/lib/services/ios-debug-service.ts +++ b/lib/services/ios-debug-service.ts @@ -24,7 +24,7 @@ export class IOSDebugService extends DebugServiceBase implements IPlatformDebugS private $hostInfo: IHostInfo, private $logger: ILogger, private $errors: IErrors, - private $npmInstallationManager: INpmInstallationManager, + private $packageInstallationManager: IPackageInstallationManager, private $iOSDebuggerPortService: IIOSDebuggerPortService, private $iOSNotification: IiOSNotification, private $iOSSocketRequestExecutor: IiOSSocketRequestExecutor, @@ -219,7 +219,7 @@ export class IOSDebugService extends DebugServiceBase implements IPlatformDebugS private async openAppInspector(fileDescriptor: string, debugData: IDebugData, debugOptions: IDebugOptions): Promise { if (debugOptions.client) { - const inspectorPath = await this.$npmInstallationManager.getInspectorFromCache(inspectorNpmPackageName, debugData.projectDir); + const inspectorPath = await this.$packageInstallationManager.getInspectorFromCache(inspectorNpmPackageName, debugData.projectDir); const inspectorSourceLocation = path.join(inspectorPath, inspectorUiDir, "Main.html"); const inspectorApplicationPath = path.join(inspectorPath, inspectorAppName); diff --git a/lib/services/nativescript-cloud-extension-service.ts b/lib/services/nativescript-cloud-extension-service.ts index 8b6a0f5889..3ff6c90a24 100644 --- a/lib/services/nativescript-cloud-extension-service.ts +++ b/lib/services/nativescript-cloud-extension-service.ts @@ -4,7 +4,7 @@ import * as semver from "semver"; export class NativeScriptCloudExtensionService implements INativeScriptCloudExtensionService { constructor(private $extensibilityService: IExtensibilityService, private $logger: ILogger, - private $npmInstallationManager: INpmInstallationManager) { } + private $packageInstallationManager: IPackageInstallationManager) { } public install(): Promise { if (!this.isInstalled()) { @@ -21,7 +21,7 @@ export class NativeScriptCloudExtensionService implements INativeScriptCloudExte public async isLatestVersionInstalled(): Promise { const extensionData = this.getExtensionData(); if (extensionData) { - const latestVersion = await this.$npmInstallationManager.getLatestVersion(constants.NATIVESCRIPT_CLOUD_EXTENSION_NAME); + const latestVersion = await this.$packageInstallationManager.getLatestVersion(constants.NATIVESCRIPT_CLOUD_EXTENSION_NAME); return semver.eq(latestVersion, extensionData.version); } diff --git a/lib/services/platform-service.ts b/lib/services/platform-service.ts index 763d8c1bb2..022b6957f4 100644 --- a/lib/services/platform-service.ts +++ b/lib/services/platform-service.ts @@ -28,7 +28,7 @@ export class PlatformService extends EventEmitter implements IPlatformService { private $errors: IErrors, private $fs: IFileSystem, private $logger: ILogger, - private $npmInstallationManager: INpmInstallationManager, + private $packageInstallationManager: IPackageInstallationManager, private $platformsData: IPlatformsData, private $projectDataService: IProjectDataService, private $hooksService: IHooksService, @@ -113,7 +113,7 @@ export class PlatformService extends EventEmitter implements IPlatformService { } else { if (!version) { version = this.getCurrentPlatformVersion(platform, projectData) || - await this.$npmInstallationManager.getLatestCompatibleVersion(platformData.frameworkPackageName); + await this.$packageInstallationManager.getLatestCompatibleVersion(platformData.frameworkPackageName); } packageToInstall = `${platformData.frameworkPackageName}@${version}`; @@ -914,8 +914,8 @@ export class PlatformService extends EventEmitter implements IPlatformService { const installedModuleDir = temp.mkdirSync("runtime-to-update"); let newVersion = version === constants.PackageVersion.NEXT ? - await this.$npmInstallationManager.getNextVersion(platformData.frameworkPackageName) : - version || await this.$npmInstallationManager.getLatestCompatibleVersion(platformData.frameworkPackageName); + await this.$packageInstallationManager.getNextVersion(platformData.frameworkPackageName) : + version || await this.$packageInstallationManager.getLatestCompatibleVersion(platformData.frameworkPackageName); await this.$pacoteService.extractPackage(`${platformData.frameworkPackageName}@${newVersion}`, installedModuleDir); const cachedPackageData = this.$fs.readJson(path.join(installedModuleDir, "package.json")); newVersion = (cachedPackageData && cachedPackageData.version) || newVersion; diff --git a/lib/services/plugins-service.ts b/lib/services/plugins-service.ts index eb3b7a2043..f18a9e8e7f 100644 --- a/lib/services/plugins-service.ts +++ b/lib/services/plugins-service.ts @@ -31,7 +31,7 @@ export class PluginsService implements IPluginsService { }, PluginsService.NPM_CONFIG); } - constructor(private $npm: INodePackageManager, + constructor(private $packageManager: INodePackageManager, private $fs: IFileSystem, private $options: IOptions, private $logger: ILogger, @@ -46,7 +46,7 @@ export class PluginsService implements IPluginsService { plugin = possiblePackageName; } - const name = (await this.$npm.install(plugin, projectData.projectDir, this.npmInstallOptions)).name; + const name = (await this.$packageManager.install(plugin, projectData.projectDir, this.npmInstallOptions)).name; const pathToRealNpmPackageJson = path.join(projectData.projectDir, "node_modules", name, "package.json"); const realNpmPackageJson = this.$fs.readJson(pathToRealNpmPackageJson); @@ -65,14 +65,14 @@ export class PluginsService implements IPluginsService { } catch (err) { // Revert package.json this.$projectDataService.removeNSProperty(projectData.projectDir, this.$pluginVariablesService.getPluginVariablePropertyName(pluginData.name)); - await this.$npm.uninstall(plugin, PluginsService.NPM_CONFIG, projectData.projectDir); + await this.$packageManager.uninstall(plugin, PluginsService.NPM_CONFIG, projectData.projectDir); throw err; } this.$logger.out(`Successfully installed plugin ${realNpmPackageJson.name}.`); } else { - await this.$npm.uninstall(realNpmPackageJson.name, { save: true }, projectData.projectDir); + await this.$packageManager.uninstall(realNpmPackageJson.name, { save: true }, projectData.projectDir); this.$errors.failWithoutHelp(`${plugin} is not a valid NativeScript plugin. Verify that the plugin package.json file contains a nativescript key and try again.`); } } @@ -181,7 +181,7 @@ export class PluginsService implements IPluginsService { const notInstalledDependencies = _.difference(allDependencies, installedDependencies); if (this.$options.force || notInstalledDependencies.length) { this.$logger.trace("Npm install will be called from CLI. Force option is: ", this.$options.force, " Not installed dependencies are: ", notInstalledDependencies); - await this.$npm.install(projectData.projectDir, projectData.projectDir, { + await this.$packageManager.install(projectData.projectDir, projectData.projectDir, { disableNpmInstall: this.$options.disableNpmInstall, frameworkPath: this.$options.frameworkPath, ignoreScripts: this.$options.ignoreScripts, @@ -282,9 +282,9 @@ export class PluginsService implements IPluginsService { private async executeNpmCommand(npmCommandName: string, npmCommandArguments: string, projectData: IProjectData): Promise { if (npmCommandName === PluginsService.INSTALL_COMMAND_NAME) { - await this.$npm.install(npmCommandArguments, projectData.projectDir, this.npmInstallOptions); + await this.$packageManager.install(npmCommandArguments, projectData.projectDir, this.npmInstallOptions); } else if (npmCommandName === PluginsService.UNINSTALL_COMMAND_NAME) { - await this.$npm.uninstall(npmCommandArguments, PluginsService.NPM_CONFIG, projectData.projectDir); + await this.$packageManager.uninstall(npmCommandArguments, PluginsService.NPM_CONFIG, projectData.projectDir); } return this.parseNpmCommandResult(npmCommandArguments); diff --git a/lib/services/prepare-platform-js-service.ts b/lib/services/prepare-platform-js-service.ts index 873062f505..9c95d259ab 100644 --- a/lib/services/prepare-platform-js-service.ts +++ b/lib/services/prepare-platform-js-service.ts @@ -16,7 +16,7 @@ export class PreparePlatformJSService extends PreparePlatformService implements private $logger: ILogger, private $projectDataService: IProjectDataService, private $nodeModulesBuilder: INodeModulesBuilder, - private $npm: INodePackageManager) { + private $packageManager: INodePackageManager) { super($fs, $hooksService, $xmlValidator); } @@ -77,7 +77,7 @@ export class PreparePlatformJSService extends PreparePlatformService implements const tempDir = temp.mkdirSync("platform-template"); this.$fs.writeJson(path.join(tempDir, constants.PACKAGE_JSON_FILE_NAME), {}); try { - const npmInstallResult = await this.$npm.install(selectedTemplate, tempDir, { + const npmInstallResult = await this.$packageManager.install(selectedTemplate, tempDir, { disableNpmInstall: false, frameworkPath: null, ignoreScripts: false diff --git a/lib/services/project-service.ts b/lib/services/project-service.ts index dad691ebf2..6fd82f2637 100644 --- a/lib/services/project-service.ts +++ b/lib/services/project-service.ts @@ -9,7 +9,7 @@ import * as temp from "temp"; export class ProjectService implements IProjectService { constructor(private $hooksService: IHooksService, - private $npm: INodePackageManager, + private $packageManager: INodePackageManager, private $errors: IErrors, private $fs: IFileSystem, private $logger: ILogger, @@ -19,7 +19,7 @@ export class ProjectService implements IProjectService { private $projectNameService: IProjectNameService, private $projectTemplatesService: IProjectTemplatesService, private $staticConfig: IStaticConfig, - private $npmInstallationManager: INpmInstallationManager) { } + private $packageInstallationManager: IPackageInstallationManager) { } public async validateProjectName(opts: { projectName: string, force: boolean, pathToProject: string }) : Promise { let projectName = opts.projectName; @@ -99,11 +99,11 @@ export class ProjectService implements IProjectService { } if (templateVersion === constants.TemplateVersions.v1) { - await this.$npm.uninstall(templatePackageJsonContent.name, { save: true }, projectDir); + await this.$packageManager.uninstall(templatePackageJsonContent.name, { save: true }, projectDir); } // Install devDependencies and execute all scripts: - await this.$npm.install(projectDir, projectDir, { + await this.$packageManager.install(projectDir, projectDir, { disableNpmInstall: false, frameworkPath: null, ignoreScripts @@ -245,7 +245,7 @@ export class ProjectService implements IProjectService { const projectFilePath = path.join(projectDir, this.$staticConfig.PROJECT_FILE_NAME); const packageJsonData = this.$fs.readJson(projectFilePath); - const version = await this.$npmInstallationManager.getLatestCompatibleVersion(constants.TNS_CORE_MODULES_NAME); + const version = await this.$packageInstallationManager.getLatestCompatibleVersion(constants.TNS_CORE_MODULES_NAME); packageJsonData.dependencies[constants.TNS_CORE_MODULES_NAME] = version; this.$fs.writeJson(projectFilePath, packageJsonData); diff --git a/lib/services/project-templates-service.ts b/lib/services/project-templates-service.ts index fba5c5cdd8..82ce73c2db 100644 --- a/lib/services/project-templates-service.ts +++ b/lib/services/project-templates-service.ts @@ -10,7 +10,7 @@ export class ProjectTemplatesService implements IProjectTemplatesService { public constructor(private $analyticsService: IAnalyticsService, private $fs: IFileSystem, private $logger: ILogger, - private $npmInstallationManager: INpmInstallationManager, + private $packageInstallationManager: IPackageInstallationManager, private $pacoteService: IPacoteService, private $errors: IErrors) { } @@ -86,15 +86,15 @@ export class ProjectTemplatesService implements IProjectTemplatesService { /** * Install verified NativeScript template in the npm cache. - * The "special" here is that npmInstallationManager will check current CLI version and will instal best matching version of the template. - * For example in case CLI is version 10.12.8, npmInstallationManager will try to find latest 10.12.x version of the template. + * The "special" here is that packageInstallationManager will check current CLI version and will instal best matching version of the template. + * For example in case CLI is version 10.12.8, packageInstallationManager will try to find latest 10.12.x version of the template. * @param {string} templateName The name of the verified NativeScript template. * @param {string} version The version of the template specified by user. * @return {string} Path to the directory where the template is installed. */ private async prepareNativeScriptTemplate(templateName: string, version?: string, projectDir?: string): Promise { this.$logger.trace(`Using NativeScript verified template: ${templateName} with version ${version}.`); - return this.$npmInstallationManager.install(templateName, projectDir, { version: version, dependencyType: "save" }); + return this.$packageInstallationManager.install(templateName, projectDir, { version: version, dependencyType: "save" }); } private getTemplateNameToBeTracked(templateName: string, packageJsonContent: any): string { diff --git a/lib/services/user-settings-service.ts b/lib/services/user-settings-service.ts index 19cc5d12fc..8f382aadf7 100644 --- a/lib/services/user-settings-service.ts +++ b/lib/services/user-settings-service.ts @@ -1,7 +1,7 @@ import * as path from "path"; import * as userSettingsServiceBaseLib from "../common/services/user-settings-service"; -class UserSettingsService extends userSettingsServiceBaseLib.UserSettingsServiceBase { +export class UserSettingsService extends userSettingsServiceBaseLib.UserSettingsServiceBase { constructor($fs: IFileSystem, $settingsService: ISettingsService, $lockfile: ILockFile, diff --git a/lib/services/versions-service.ts b/lib/services/versions-service.ts index 441f17dc6f..e5d566e2cd 100644 --- a/lib/services/versions-service.ts +++ b/lib/services/versions-service.ts @@ -17,7 +17,7 @@ class VersionsService implements IVersionsService { private projectData: IProjectData; constructor(private $fs: IFileSystem, - private $npmInstallationManager: INpmInstallationManager, + private $packageInstallationManager: IPackageInstallationManager, private $injector: IInjector, private $logger: ILogger, private $staticConfig: Config.IStaticConfig, @@ -28,7 +28,7 @@ class VersionsService implements IVersionsService { public async getNativescriptCliVersion(): Promise { const currentCliVersion = this.$staticConfig.version; - const latestCliVersion = await this.$npmInstallationManager.getLatestVersion(constants.NATIVESCRIPT_KEY_NAME); + const latestCliVersion = await this.$packageInstallationManager.getLatestVersion(constants.NATIVESCRIPT_KEY_NAME); return { componentName: constants.NATIVESCRIPT_KEY_NAME, @@ -38,7 +38,7 @@ class VersionsService implements IVersionsService { } public async getTnsCoreModulesVersion(): Promise { - const latestTnsCoreModulesVersion = await this.$npmInstallationManager.getLatestVersion(constants.TNS_CORE_MODULES_NAME); + const latestTnsCoreModulesVersion = await this.$packageInstallationManager.getLatestVersion(constants.TNS_CORE_MODULES_NAME); const nativescriptCoreModulesInfo: IVersionInformation = { componentName: constants.TNS_CORE_MODULES_NAME, latestVersion: latestTnsCoreModulesVersion @@ -72,7 +72,7 @@ class VersionsService implements IVersionsService { } const runtimesVersions: IVersionInformation[] = await Promise.all(runtimes.map(async (runtime: string) => { - const latestRuntimeVersion = await this.$npmInstallationManager.getLatestVersion(runtime); + const latestRuntimeVersion = await this.$packageInstallationManager.getLatestVersion(runtime); const runtimeInformation: IVersionInformation = { componentName: runtime, latestVersion: latestRuntimeVersion diff --git a/lib/yarn-package-manager.ts b/lib/yarn-package-manager.ts new file mode 100644 index 0000000000..a20347c45a --- /dev/null +++ b/lib/yarn-package-manager.ts @@ -0,0 +1,111 @@ +import * as path from "path"; +import { BasePackageManager } from "./base-package-manager"; +import { exported } from './common/decorators'; + +export class YarnPackageManager extends BasePackageManager implements INodePackageManager { + + constructor( + $childProcess: IChildProcess, + private $errors: IErrors, + private $fs: IFileSystem, + $hostInfo: IHostInfo, + private $httpClient: Server.IHttpClient, + private $logger: ILogger, + private $pacoteService: IPacoteService + ) { + super($childProcess, $hostInfo, 'yarn'); + } + + @exported("yarn") + public async install(packageName: string, pathToSave: string, config: INodePackageManagerInstallOptions): Promise { + if (config.disableNpmInstall) { + return; + } + if (config.ignoreScripts) { + config['ignore-scripts'] = true; + } + + const packageJsonPath = path.join(pathToSave, 'package.json'); + const jsonContentBefore = this.$fs.readJson(packageJsonPath); + + const flags = this.getFlagsString(config, true); + let params = []; + const isInstallingAllDependencies = packageName === pathToSave; + if (!isInstallingAllDependencies) { + params.push('add', packageName); + } + + params = params.concat(flags); + const cwd = pathToSave; + + try { + await this.processPackageManagerInstall(params, { cwd }); + + if (isInstallingAllDependencies) { + return null; + } + + const packageMetadata = await this.$pacoteService.manifest(packageName, {}); + return { + name: packageMetadata.name, + version: packageMetadata.version + }; + + } catch (e) { + this.$fs.writeJson(packageJsonPath, jsonContentBefore); + throw e; + } + } + + @exported("yarn") + public uninstall(packageName: string, config?: IDictionary, path?: string): Promise { + const flags = this.getFlagsString(config, false); + return this.$childProcess.exec(`yarn remove ${packageName} ${flags}`, { cwd: path }); + } + + @exported("yarn") + public async view(packageName: string, config: Object): Promise { + const wrappedConfig = _.extend({}, config, { json: true }); + + const flags = this.getFlagsString(wrappedConfig, false); + let viewResult: any; + try { + viewResult = await this.$childProcess.exec(`yarn info ${packageName} ${flags}`); + } catch (e) { + this.$errors.failWithoutHelp(e.message); + } + return JSON.parse(viewResult); + } + + @exported("yarn") + public search(filter: string[], config: IDictionary): Promise { + this.$errors.fail("Method not implemented. Yarn does not support searching for packages in the registry."); + return null; + } + + public async searchNpms(keyword: string): Promise { + const httpRequestResult = await this.$httpClient.httpRequest(`https://api.npms.io/v2/search?q=keywords:${keyword}`); + const result: INpmsResult = JSON.parse(httpRequestResult.body); + return result; + } + + @exported("yarn") + public async getRegistryPackageData(packageName: string): Promise { + const registry = await this.$childProcess.exec(`yarn config get registry`); + const url = `${registry.trim()}/${packageName}`; + this.$logger.trace(`Trying to get data from yarn registry for package ${packageName}, url is: ${url}`); + const responseData = (await this.$httpClient.httpRequest(url)).body; + this.$logger.trace(`Successfully received data from yarn registry for package ${packageName}. Response data is: ${responseData}`); + const jsonData = JSON.parse(responseData); + this.$logger.trace(`Successfully parsed data from yarn registry for package ${packageName}.`); + return jsonData; + } + + @exported("yarn") + getCachePath(): Promise { + this.$errors.fail("Method not implemented"); + return null; + } +} + +$injector.register("yarn", YarnPackageManager); diff --git a/test/debug.ts b/test/debug.ts index a5d25638b9..0f1ae8d38c 100644 --- a/test/debug.ts +++ b/test/debug.ts @@ -52,7 +52,7 @@ function createTestInjector(): IInjector { testInjector.register("projectTemplatesService", {}); testInjector.register("debugService", {}); testInjector.register("xmlValidator", {}); - testInjector.register("npm", {}); + testInjector.register("packageManager", {}); testInjector.register("debugDataService", { createDebugData: () => ({}) }); diff --git a/test/ios-project-service.ts b/test/ios-project-service.ts index 1d538e3439..20dd7438c4 100644 --- a/test/ios-project-service.ts +++ b/test/ios-project-service.ts @@ -27,7 +27,9 @@ import { PluginsService } from "../lib/services/plugins-service"; import { PluginVariablesHelper } from "../lib/common/plugin-variables-helper"; import { Utils } from "../lib/common/utils"; import { CocoaPodsService } from "../lib/services/cocoapods-service"; +import { PackageManager } from "../lib/package-manager"; import { NodePackageManager } from "../lib/node-package-manager"; +import { YarnPackageManager } from "../lib/yarn-package-manager"; import { assert } from "chai"; import { IOSProvisionService } from "../lib/services/ios-provision-service"; @@ -115,7 +117,12 @@ function createTestInjector(projectPath: string, projectName: string, xcode?: IX pbxGroupByName() { /* */ } } }); + testInjector.register("userSettingsService", { + getSettingValue: async (settingName: string): Promise => undefined + }); + testInjector.register("packageManager", PackageManager); testInjector.register("npm", NodePackageManager); + testInjector.register("yarn", YarnPackageManager); testInjector.register("xCConfigService", XCConfigService); testInjector.register("settingsService", SettingsService); testInjector.register("httpClient", {}); @@ -130,7 +137,9 @@ function createTestInjector(projectPath: string, projectName: string, xcode?: IX hasChangesInShasums: (oldPluginNativeHashes: IStringDictionary, currentPluginNativeHashes: IStringDictionary) => true, generateHashes: async (files: string[]): Promise => ({}) }); - + testInjector.register("pacoteService", { + extractPackage: async (packageName: string, destinationDirectory: string, options?: IPacoteExtractOptions): Promise => undefined + }); return testInjector; } diff --git a/test/nativescript-cli-lib.ts b/test/nativescript-cli-lib.ts index 0919267280..fec8a96825 100644 --- a/test/nativescript-cli-lib.ts +++ b/test/nativescript-cli-lib.ts @@ -27,7 +27,7 @@ describe("nativescript-cli-lib", () => { constants: ["CONFIG_NS_APP_RESOURCES_ENTRY", "CONFIG_NS_APP_ENTRY", "CONFIG_NS_FILE_NAME"], localBuildService: ["build"], deviceLogProvider: null, - npm: ["install", "uninstall", "view", "search"], + packageManager: ["install", "uninstall", "view", "search"], extensibilityService: ["loadExtensions", "loadExtension", "getInstalledExtensions", "installExtension", "uninstallExtension"], liveSyncService: ["liveSync", "stopLiveSync", "enableDebugging", "disableDebugging", "attachDebugger"], debugService: ["debug"], diff --git a/test/npm-installation-manager.ts b/test/npm-installation-manager.ts index 6c502fe09f..fa35296680 100644 --- a/test/npm-installation-manager.ts +++ b/test/npm-installation-manager.ts @@ -4,7 +4,10 @@ import * as ErrorsLib from "../lib/common/errors"; import * as FsLib from "../lib/common/file-system"; import * as HostInfoLib from "../lib/common/host-info"; import * as LoggerLib from "../lib/common/logger"; -import * as NpmInstallationManagerLib from "../lib/npm-installation-manager"; +import * as NpmLib from "../lib/node-package-manager"; +import * as YarnLib from "../lib/yarn-package-manager"; +import * as PackageManagerLib from "../lib/package-manager"; +import * as PackageInstallationManagerLib from "../lib/package-installation-manager"; import * as OptionsLib from "../lib/options"; import * as StaticConfigLib from "../lib/config"; import * as yok from "../lib/common/yok"; @@ -28,7 +31,17 @@ function createTestInjector(): IInjector { testInjector.register("devicePlatformsConstants", {}); testInjector.register("androidResourcesMigrationService", {}); - testInjector.register("npmInstallationManager", NpmInstallationManagerLib.NpmInstallationManager); + testInjector.register("httpClient", {}); + testInjector.register("pacoteService", { + manifest: () => Promise.resolve(), + }); + testInjector.register("userSettingsService", { + getSettingValue: async (settingName: string): Promise => undefined + }); + testInjector.register("npm", NpmLib.NodePackageManager); + testInjector.register("yarn", YarnLib.YarnPackageManager); + testInjector.register("packageManager", PackageManagerLib.PackageManager); + testInjector.register("packageInstallationManager", PackageInstallationManagerLib.PackageInstallationManager); return testInjector; } @@ -185,11 +198,11 @@ describe("Npm installation manager tests", () => { const staticConfig = testInjector.resolve("staticConfig"); staticConfig.version = currentTestData.cliVersion; - // Mock npmInstallationManager.getLatestVersion - const npmInstallationManager = testInjector.resolve("npmInstallationManager"); - npmInstallationManager.getLatestVersion = (packageName: string) => Promise.resolve(currentTestData.packageLatestVersion); + // Mock packageInstallationManager.getLatestVersion + const packageInstallationManager = testInjector.resolve("packageInstallationManager"); + packageInstallationManager.getLatestVersion = (packageName: string) => Promise.resolve(currentTestData.packageLatestVersion); - const actualLatestCompatibleVersion = await npmInstallationManager.getLatestCompatibleVersion("", currentTestData.referenceVersion); + const actualLatestCompatibleVersion = await packageInstallationManager.getLatestCompatibleVersion("", currentTestData.referenceVersion); assert.equal(actualLatestCompatibleVersion, currentTestData.expectedResult); }); }); diff --git a/test/npm-support.ts b/test/npm-support.ts index a22eb9867b..8039530151 100644 --- a/test/npm-support.ts +++ b/test/npm-support.ts @@ -2,6 +2,8 @@ import yok = require('../lib/common/yok'); import stubs = require('./stubs'); import ErrorsLib = require("../lib/common/errors"); import NpmLib = require("../lib/node-package-manager"); +import PackageManagerLib = require("../lib/package-manager"); +import YarnLib = require("../lib/yarn-package-manager"); import FsLib = require("../lib/common/file-system"); import OptionsLib = require("../lib/options"); import StaticConfigLib = require("../lib/config"); @@ -49,7 +51,7 @@ function createTestInjector(): IInjector { testInjector.register("platformsData", PlatformsDataLib.PlatformsData); testInjector.register("platformService", PlatformServiceLib.PlatformService); testInjector.register("logger", stubs.LoggerStub); - testInjector.register("npmInstallationManager", { + testInjector.register("packageInstallationManager", { install: () => Promise.resolve() }); testInjector.register("prompter", {}); @@ -65,7 +67,12 @@ function createTestInjector(): IInjector { testInjector.register("hooksService", stubs.HooksServiceStub); testInjector.register("nodeModulesBuilder", NodeModulesLib.NodeModulesBuilder); testInjector.register("pluginsService", PluginsServiceLib.PluginsService); + testInjector.register("userSettingsService", { + getSettingValue: async (settingName: string): Promise => undefined + }); testInjector.register("npm", NpmLib.NodePackageManager); + testInjector.register("packageManager", PackageManagerLib.PackageManager); + testInjector.register("yarn", YarnLib.YarnPackageManager); testInjector.register("childProcess", ChildProcessLib.ChildProcess); testInjector.register("projectFilesManager", ProjectFilesManagerLib.ProjectFilesManager); testInjector.register("commandsServiceProvider", { diff --git a/test/platform-commands.ts b/test/platform-commands.ts index 43876214be..f4da8918df 100644 --- a/test/platform-commands.ts +++ b/test/platform-commands.ts @@ -105,7 +105,7 @@ function createTestInjector() { testInjector.register('platformService', PlatformServiceLib.PlatformService); testInjector.register('errors', ErrorsNoFailStub); testInjector.register('logger', stubs.LoggerStub); - testInjector.register('npmInstallationManager', stubs.NpmInstallationManagerStub); + testInjector.register('packageInstallationManager', stubs.PackageInstallationManagerStub); testInjector.register('projectData', stubs.ProjectDataStub); testInjector.register('platformsData', PlatformsData); testInjector.register('devicesService', {}); diff --git a/test/platform-service.ts b/test/platform-service.ts index c85fcc0214..becab6da49 100644 --- a/test/platform-service.ts +++ b/test/platform-service.ts @@ -38,7 +38,7 @@ function createTestInjector() { testInjector.register('errors', stubs.ErrorsStub); testInjector.register('logger', stubs.LoggerStub); testInjector.register("nodeModulesDependenciesBuilder", {}); - testInjector.register('npmInstallationManager', stubs.NpmInstallationManagerStub); + testInjector.register('packageInstallationManager', stubs.PackageInstallationManagerStub); // TODO: Remove the projectData - it shouldn't be required in the service itself. testInjector.register('projectData', stubs.ProjectDataStub); testInjector.register('platformsData', stubs.PlatformsDataStub); @@ -84,7 +84,7 @@ function createTestInjector() { testInjector.register("xmlValidator", XmlValidator); testInjector.register("preparePlatformNativeService", PreparePlatformNativeService); testInjector.register("preparePlatformJSService", PreparePlatformJSService); - testInjector.register("npm", { + testInjector.register("packageManager", { uninstall: async () => { return true; } @@ -295,8 +295,8 @@ describe('Platform Service Tests', () => { projectDataService.getNSValue = (): any => null; const latestCompatibleVersion = "1.0.0"; - const npmInstallationManager = testInjector.resolve("npmInstallationManager"); - npmInstallationManager.getLatestCompatibleVersion = async (packageName: string, referenceVersion?: string): Promise => { + const packageInstallationManager = testInjector.resolve("packageInstallationManager"); + packageInstallationManager.getLatestCompatibleVersion = async (packageName: string, referenceVersion?: string): Promise => { return latestCompatibleVersion; }; @@ -347,8 +347,8 @@ describe('Platform Service Tests', () => { const projectDataService = testInjector.resolve("projectDataService"); projectDataService.getNSValue = () => nsValueObject; - const npmInstallationManager = testInjector.resolve("npmInstallationManager"); - npmInstallationManager.install = (packageName: string, packageDir: string, options: INpmInstallOptions) => { + const packageInstallationManager = testInjector.resolve("packageInstallationManager"); + packageInstallationManager.install = (packageName: string, packageDir: string, options: INpmInstallOptions) => { assert.deepEqual(options.version, versionString); return ""; }; @@ -376,8 +376,8 @@ describe('Platform Service Tests', () => { describe("update Platform", () => { describe("#updatePlatform(platform)", () => { it("should fail when the versions are the same", async () => { - const npmInstallationManager: INpmInstallationManager = testInjector.resolve("npmInstallationManager"); - npmInstallationManager.getLatestVersion = async () => "0.2.0"; + const packageInstallationManager: IPackageInstallationManager = testInjector.resolve("packageInstallationManager"); + packageInstallationManager.getLatestVersion = async () => "0.2.0"; const projectData: IProjectData = testInjector.resolve("projectData"); await assert.isRejected(platformService.updatePlatforms(["android"], "", projectData, null)); diff --git a/test/plugin-create.ts b/test/plugin-create.ts index c6ab5b6f6b..f1850bb9fa 100644 --- a/test/plugin-create.ts +++ b/test/plugin-create.ts @@ -24,7 +24,7 @@ function createTestInjector() { testInjector.register("childProcess", stubs.ChildProcessStub); testInjector.register("prompter", new stubs.PrompterStub()); testInjector.register("fs", stubs.FileSystemStub); - testInjector.register("npm", stubs.NpmInstallationManagerStub); + testInjector.register("packageManager", stubs.PackageInstallationManagerStub); testInjector.register("options", { username: undefined, pluginName: undefined, diff --git a/test/plugins-service.ts b/test/plugins-service.ts index 41dcfeae72..297fadc310 100644 --- a/test/plugins-service.ts +++ b/test/plugins-service.ts @@ -1,7 +1,9 @@ import { Yok } from '../lib/common/yok'; import * as stubs from './stubs'; +import { PackageManager } from "../lib/package-manager"; +import { PackageInstallationManager } from "../lib/package-installation-manager"; import { NodePackageManager } from "../lib/node-package-manager"; -import { NpmInstallationManager } from "../lib/npm-installation-manager"; +import { YarnPackageManager } from "../lib/yarn-package-manager"; import { FileSystem } from "../lib/common/file-system"; import { ProjectData } from "../lib/project-data"; import { ChildProcess } from "../lib/common/child-process"; @@ -42,7 +44,12 @@ let isErrorThrown = false; function createTestInjector() { const testInjector = new Yok(); testInjector.register("messagesService", MessagesService); + testInjector.register("userSettingsService", { + getSettingValue: async (settingName: string): Promise => undefined + }); + testInjector.register("packageManager", PackageManager); testInjector.register("npm", NodePackageManager); + testInjector.register("yarn", YarnPackageManager); testInjector.register("fs", FileSystem); testInjector.register("adb", {}); testInjector.register("androidDebugBridgeResultHandler", {}); @@ -87,7 +94,7 @@ function createTestInjector() { savePluginVariablesInProjectFile: (pluginData: IPluginData) => Promise.resolve(), interpolatePluginVariables: (pluginData: IPluginData, pluginConfigurationFileContent: string) => Promise.resolve(pluginConfigurationFileContent) }); - testInjector.register("npmInstallationManager", NpmInstallationManager); + testInjector.register("packageInstallationManager", PackageInstallationManager); testInjector.register("deviceAppDataFactory", DeviceAppDataFactory); testInjector.register("localToDevicePathDataFactory", LocalToDevicePathDataFactory); @@ -124,6 +131,9 @@ function createTestInjector() { hasChangesInShasums: (oldPluginNativeHashes: IStringDictionary, currentPluginNativeHashes: IStringDictionary) => true, generateHashes: async (files: string[]): Promise => ({}) }); + testInjector.register("pacoteService", { + extractPackage: async (packageName: string, destinationDirectory: string, options?: IPacoteExtractOptions): Promise => undefined + }); return testInjector; } @@ -597,7 +607,7 @@ describe("Plugins service", () => { enumerateFilesInDirectorySync: (): string[] => ["some_file"] }); - unitTestsInjector.register("npm", {}); + unitTestsInjector.register("packageManager", {}); unitTestsInjector.register("options", {}); unitTestsInjector.register("logger", {}); unitTestsInjector.register("errors", {}); diff --git a/test/project-service.ts b/test/project-service.ts index f7edfd9001..35d6586d79 100644 --- a/test/project-service.ts +++ b/test/project-service.ts @@ -1,6 +1,6 @@ import * as yok from "../lib/common/yok"; import * as stubs from "./stubs"; -import * as constants from "./../lib/constants"; +import * as constants from "../lib/constants"; import { ChildProcess } from "../lib/common/child-process"; import * as ProjectServiceLib from "../lib/services/project-service"; import { ProjectNameService } from "../lib/services/project-name-service"; @@ -8,7 +8,9 @@ import * as ProjectDataServiceLib from "../lib/services/project-data-service"; import * as ProjectHelperLib from "../lib/common/project-helper"; import { StaticConfig } from "../lib/config"; import * as NpmLib from "../lib/node-package-manager"; -import { NpmInstallationManager } from "../lib/npm-installation-manager"; +import * as YarnLib from "../lib/yarn-package-manager"; +import * as PackageManagerLib from "../lib/package-manager"; +import { PackageInstallationManager } from "../lib/package-installation-manager"; import { FileSystem } from "../lib/common/file-system"; import * as path from "path"; import temp = require("temp"); @@ -35,7 +37,7 @@ async function prepareTestingPath(testInjector: IInjector, packageToInstall: str options = options || { dependencyType: "save" }; const fs = testInjector.resolve("fs"); - const npmInstallationManager = testInjector.resolve("npmInstallationManager"); + const packageInstallationManager = testInjector.resolve("packageInstallationManager"); const defaultTemplateDir = temp.mkdirSync("project-service"); fs.writeJson(path.join(defaultTemplateDir, constants.PACKAGE_JSON_FILE_NAME), { "name": "defaultTemplate", @@ -46,7 +48,7 @@ async function prepareTestingPath(testInjector: IInjector, packageToInstall: str "repository": "dummy" }); - await npmInstallationManager.install(packageToInstall, defaultTemplateDir, options); + await packageInstallationManager.install(packageToInstall, defaultTemplateDir, options); const defaultTemplatePath = path.join(defaultTemplateDir, constants.NODE_MODULES_FOLDER_NAME, packageName); fs.deleteDirectory(path.join(defaultTemplatePath, constants.NODE_MODULES_FOLDER_NAME)); @@ -129,8 +131,12 @@ class ProjectIntegrationTest { trackEventActionInGoogleAnalytics: (data: IEventActionData) => Promise.resolve() }); - this.testInjector.register("npmInstallationManager", NpmInstallationManager); + this.testInjector.register("userSettingsService", { + getSettingValue: async (settingName: string): Promise => undefined + }); this.testInjector.register("npm", NpmLib.NodePackageManager); + this.testInjector.register("yarn", YarnLib.YarnPackageManager); + this.testInjector.register("packageManager", PackageManagerLib.PackageManager); this.testInjector.register("httpClient", {}); this.testInjector.register("options", Options); @@ -142,7 +148,7 @@ class ProjectIntegrationTest { return dummyString; } }); - this.testInjector.register("npmInstallationManager", NpmInstallationManager); + this.testInjector.register("packageInstallationManager", PackageInstallationManager); this.testInjector.register("settingsService", SettingsService); this.testInjector.register("devicePlatformsConstants", DevicePlatformsConstants); this.testInjector.register("androidResourcesMigrationService", { @@ -256,7 +262,7 @@ describe("Project Service Tests", () => { describe("isValidNativeScriptProject", () => { const getTestInjector = (projectData?: any): IInjector => { const testInjector = new yok.Yok(); - testInjector.register("npm", {}); + testInjector.register("packageManager", {}); testInjector.register("errors", {}); testInjector.register("fs", {}); testInjector.register("logger", {}); @@ -268,7 +274,7 @@ describe("Project Service Tests", () => { testInjector.register("projectTemplatesService", {}); testInjector.register("staticConfig", {}); testInjector.register("projectHelper", {}); - testInjector.register("npmInstallationManager", {}); + testInjector.register("packageInstallationManager", {}); testInjector.register("settingsService", SettingsService); testInjector.register("hooksService", { executeAfterHooks: async (commandName: string, hookArguments?: IDictionary): Promise => undefined diff --git a/test/project-templates-service.ts b/test/project-templates-service.ts index ad9e12b27d..edea4512b1 100644 --- a/test/project-templates-service.ts +++ b/test/project-templates-service.ts @@ -35,7 +35,7 @@ function createTestInjector(configuration: { shouldNpmInstallThrow?: boolean, pa } }); - injector.register("npmInstallationManager", { + injector.register("packageInstallationManager", { install: (packageName: string, options?: INpmInstallOptions) => { if (configuration.shouldNpmInstallThrow) { throw new Error("NPM install throws error."); diff --git a/test/services/android-plugin-build-service.ts b/test/services/android-plugin-build-service.ts index 62950ef546..590c9425df 100644 --- a/test/services/android-plugin-build-service.ts +++ b/test/services/android-plugin-build-service.ts @@ -73,7 +73,7 @@ describe('androidPluginBuildService', () => { return options.addProjectRuntime ? "1.0.0" : null; } }); - testInjector.register('npm', setupNpm(options)); + testInjector.register('packageManager', setupNpm(options)); testInjector.register('filesHashService', { generateHashes: async (files: string[]): Promise => ({}), getChanges: async (files: string[], oldHashes: IStringDictionary): Promise => ({}), diff --git a/test/services/extensibility-service.ts b/test/services/extensibility-service.ts index 75102e5041..aae1f10e0d 100644 --- a/test/services/extensibility-service.ts +++ b/test/services/extensibility-service.ts @@ -2,8 +2,14 @@ import { ExtensibilityService } from '../../lib/services/extensibility-service'; import { Yok } from "../../lib/common/yok"; import * as stubs from "../stubs"; import { assert } from "chai"; +import { NodePackageManager } from "../../lib/node-package-manager"; +import { PackageManager } from "../../lib/package-manager"; +import { YarnPackageManager } from "../../lib/yarn-package-manager"; import * as constants from "../../lib/constants"; +import { ChildProcess } from "../../lib/common/child-process"; import { CommandsDelimiters } from "../../lib/common/constants"; +import { Errors } from "../../lib/common/errors"; +import { HostInfo } from "../../lib/common/host-info"; import { SettingsService } from "../../lib/common/test/unit-tests/stubs"; const path = require("path"); const originalResolve = path.resolve; @@ -36,7 +42,22 @@ describe("extensibilityService", () => { readJson: (pathToFile: string): any => ({}) }); testInjector.register("logger", stubs.LoggerStub); - testInjector.register("npm", {}); + testInjector.register("childProcess", ChildProcess); + testInjector.register("errors", Errors); + testInjector.register("hostInfo", HostInfo); + testInjector.register("httpClient", {}); + testInjector.register("packageManager", PackageManager); + testInjector.register("options", {}); + testInjector.register("pacoteService", { + manifest: async (packageName: string, options?: IPacoteManifestOptions): Promise => { + return {}; + } + }); + testInjector.register("userSettingsService", { + getSettingValue: async (settingName: string): Promise => undefined + }); + testInjector.register("npm", NodePackageManager); + testInjector.register("yarn", YarnPackageManager); testInjector.register("settingsService", SettingsService); testInjector.register("requireService", { require: (pathToRequire: string): any => undefined diff --git a/test/services/ios-debug-service.ts b/test/services/ios-debug-service.ts index 4c970febcb..d6384542be 100644 --- a/test/services/ios-debug-service.ts +++ b/test/services/ios-debug-service.ts @@ -13,7 +13,7 @@ class IOSDebugServiceInheritor extends IOSDebugService { $hostInfo: IHostInfo, $logger: ILogger, $errors: IErrors, - $npmInstallationManager: INpmInstallationManager, + $packageInstallationManager: IPackageInstallationManager, $iOSDebuggerPortService: IIOSDebuggerPortService, $iOSNotification: IiOSNotification, $iOSSocketRequestExecutor: IiOSSocketRequestExecutor, @@ -23,7 +23,7 @@ class IOSDebugServiceInheritor extends IOSDebugService { $projectDataService: IProjectDataService, $deviceLogProvider: Mobile.IDeviceLogProvider) { super({}, $devicesService, $platformService, $iOSEmulatorServices, $childProcess, $hostInfo, $logger, $errors, - $npmInstallationManager, $iOSDebuggerPortService, $iOSNotification, $iOSSocketRequestExecutor, $processService, + $packageInstallationManager, $iOSDebuggerPortService, $iOSNotification, $iOSSocketRequestExecutor, $processService, $socketProxyFactory, $projectDataService, $deviceLogProvider); } @@ -42,7 +42,7 @@ const createTestInjector = (): IInjector => { testInjector.register("errors", stubs.ErrorsStub); testInjector.register("logger", stubs.LoggerStub); testInjector.register("hostInfo", {}); - testInjector.register("npmInstallationManager", {}); + testInjector.register("packageInstallationManager", {}); testInjector.register("iOSNotification", {}); testInjector.register("iOSSocketRequestExecutor", {}); testInjector.register("processService", { diff --git a/test/services/pacote-service.ts b/test/services/pacote-service.ts index 1d00ae4bc8..acdbaa96b4 100644 --- a/test/services/pacote-service.ts +++ b/test/services/pacote-service.ts @@ -49,7 +49,15 @@ const createTestInjector = (opts?: ITestSetup): IInjector => { testInjector.register("proxyService", { getCache: async (): Promise => opts.useProxySettings ? proxySettings : null }); + testInjector.register("packageManager", { + getCachePath: async (): Promise => { + if (opts.npmGetCachePathError) { + throw opts.npmGetCachePathError; + } + return npmCachePath; + } + }); testInjector.register("npm", { getCachePath: async (): Promise => { if (opts.npmGetCachePathError) { diff --git a/test/stubs.ts b/test/stubs.ts index 3880b3eeaf..a37d28ea20 100644 --- a/test/stubs.ts +++ b/test/stubs.ts @@ -226,7 +226,7 @@ export class ErrorsStub implements IErrors { validateYargsArguments(parsed: any, knownOpts: any, shorthands: any, clientName?: string): void { } } -export class NpmInstallationManagerStub implements INpmInstallationManager { +export class PackageInstallationManagerStub implements IPackageInstallationManager { async install(packageName: string, pathToSave?: string, options?: INpmInstallOptions): Promise { return Promise.resolve(""); } @@ -873,6 +873,6 @@ export class InjectorStub extends Yok implements IInjector { this.register('platformsData', PlatformsDataStub); this.register("androidPluginBuildService", AndroidPluginBuildServiceStub); this.register('projectData', ProjectDataStub); - this.register('npmInstallationManager', NpmInstallationManagerStub); + this.register('packageInstallationManager', PackageInstallationManagerStub); } }