diff --git a/lib/base-package-manager.ts b/lib/base-package-manager.ts index 5c4dbbf9ad..aa1a52a3c4 100644 --- a/lib/base-package-manager.ts +++ b/lib/base-package-manager.ts @@ -130,7 +130,7 @@ export abstract class BasePackageManager implements INodePackageManager { protected getFlagsString(config: any, asArray: boolean): any { const array: Array = []; for (const flag in config) { - if (flag === "global" && this.packageManager !== "yarn") { + if (flag === "global" && this.packageManager !== "yarn" && this.packageManager !== "yarn2") { array.push(`--${flag}`); array.push(`${config[flag]}`); } else if (config[flag]) { @@ -141,7 +141,12 @@ export abstract class BasePackageManager implements INodePackageManager { flag === "gradle" || flag === "version_info" ) { - array.push(` ${flag}`); + if (this.packageManager === "yarn2") { + array.push(`--fields ${flag}`); + } else { + array.push(` ${flag}`); + + } continue; } array.push(`--${flag}`); diff --git a/lib/bootstrap.ts b/lib/bootstrap.ts index e2d6e8d7fa..aec9c09d9b 100644 --- a/lib/bootstrap.ts +++ b/lib/bootstrap.ts @@ -219,6 +219,7 @@ injector.requireCommand("setup|*", "./commands/setup"); injector.requirePublic("packageManager", "./package-manager"); injector.requirePublic("npm", "./node-package-manager"); injector.requirePublic("yarn", "./yarn-package-manager"); +injector.requirePublic("yarn2", "./yarn2-package-manager"); injector.requirePublic("pnpm", "./pnpm-package-manager"); injector.requireCommand( "package-manager|*get", diff --git a/lib/commands/preview.ts b/lib/commands/preview.ts index e4a7487d96..9e3fe1caac 100644 --- a/lib/commands/preview.ts +++ b/lib/commands/preview.ts @@ -51,6 +51,7 @@ export class PreviewCommand implements ICommand { installCommand = "npm install --save-dev @nativescript/preview-cli"; break; case PackageManagers.yarn: + case PackageManagers.yarn2: installCommand = "yarn add -D @nativescript/preview-cli"; break; case PackageManagers.pnpm: diff --git a/lib/common/dispatchers.ts b/lib/common/dispatchers.ts index d25fb4fa6c..8ff18c45f4 100644 --- a/lib/common/dispatchers.ts +++ b/lib/common/dispatchers.ts @@ -124,6 +124,7 @@ export class CommandDispatcher implements ICommandDispatcher { updateCommand = "npm i -g nativescript"; break; case PackageManagers.yarn: + case PackageManagers.yarn2: updateCommand = "yarn global add nativescript"; break; case PackageManagers.pnpm: diff --git a/lib/constants.ts b/lib/constants.ts index bbb8f8dcbc..5205f21b88 100644 --- a/lib/constants.ts +++ b/lib/constants.ts @@ -493,4 +493,5 @@ export enum PackageManagers { npm = "npm", pnpm = "pnpm", yarn = "yarn", + yarn2 = "yarn2", } diff --git a/lib/declarations.d.ts b/lib/declarations.d.ts index e27b2729cd..1e22ea542a 100644 --- a/lib/declarations.d.ts +++ b/lib/declarations.d.ts @@ -663,6 +663,7 @@ interface IOptions frameworkName: string; frameworkVersion: string; yarn: string; + yarn2: string; pnpm: string; ipa: string; tsc: boolean; diff --git a/lib/options.ts b/lib/options.ts index 7d91f548d9..0c9169b8f2 100644 --- a/lib/options.ts +++ b/lib/options.ts @@ -136,6 +136,7 @@ export class Options { ts: { type: OptionType.Boolean, hasSensitiveValue: false }, typescript: { type: OptionType.Boolean, hasSensitiveValue: false }, yarn: { type: OptionType.Boolean, hasSensitiveValue: false }, + yarn2: { type: OptionType.Boolean, hasSensitiveValue: false }, pnpm: { type: OptionType.Boolean, hasSensitiveValue: false }, androidTypings: { type: OptionType.Boolean, hasSensitiveValue: false }, bundle: { type: OptionType.String, hasSensitiveValue: false }, diff --git a/lib/package-installation-manager.ts b/lib/package-installation-manager.ts index c8519bf02d..1418f4a711 100644 --- a/lib/package-installation-manager.ts +++ b/lib/package-installation-manager.ts @@ -70,7 +70,7 @@ export class PackageInstallationManager implements IPackageInstallationManager { versions: true, }); - return semver.maxSatisfying(data, versionRange); + return semver.maxSatisfying(data?.versions ?? data, versionRange); } public async getMaxSatisfyingVersionSafe( @@ -320,9 +320,10 @@ export class PackageInstallationManager implements IPackageInstallationManager { packageName: string, version: string ): Promise { - const data: any = await this.$packageManager.view(packageName, { + let data: any = await this.$packageManager.view(packageName, { "dist-tags": true, }); + data = data?.["dist-tags"] ?? data; this.$logger.trace("Using version %s. ", data[version]); return data[version]; diff --git a/lib/package-manager.ts b/lib/package-manager.ts index 8bc54a92cf..6d95e9dc86 100644 --- a/lib/package-manager.ts +++ b/lib/package-manager.ts @@ -26,6 +26,7 @@ export class PackageManager implements IPackageManager { private $npm: INodePackageManager, private $options: IOptions, private $yarn: INodePackageManager, + private $yarn2: INodePackageManager, private $pnpm: INodePackageManager, private $logger: ILogger, private $userSettingsService: IUserSettingsService, @@ -164,6 +165,9 @@ export class PackageManager implements IPackageManager { if (pm === PackageManagers.yarn || this.$options.yarn) { this._packageManagerName = PackageManagers.yarn; return this.$yarn; + } if (pm === PackageManagers.yarn2 || this.$options.yarn2) { + this._packageManagerName = PackageManagers.yarn2; + return this.$yarn2; } else if (pm === PackageManagers.pnpm || this.$options.pnpm) { this._packageManagerName = PackageManagers.pnpm; return this.$pnpm; diff --git a/lib/services/android-plugin-build-service.ts b/lib/services/android-plugin-build-service.ts index 4aecfed3ac..0f9be118d5 100644 --- a/lib/services/android-plugin-build-service.ts +++ b/lib/services/android-plugin-build-service.ts @@ -375,7 +375,7 @@ export class AndroidPluginBuildService implements IAndroidPluginBuildService { const allGradleTemplateFiles = path.join(gradleTemplatePath, "*"); const buildGradlePath = path.join(pluginTempDir, "build.gradle"); const settingsGradlePath = path.join(pluginTempDir, "settings.gradle"); - + this.$fs.copyFile(allGradleTemplateFiles, pluginTempDir); const runtimeGradleVersions = await this.getRuntimeGradleVersions( projectDir @@ -430,12 +430,13 @@ export class AndroidPluginBuildService implements IAndroidPluginBuildService { let runtimeVersion: string = null; try { - const result = await this.$packageManager.view( + let result = await this.$packageManager.view( SCOPED_ANDROID_RUNTIME_NAME, { "dist-tags": true, } ); + result = result?.["dist-tags"] ?? result; runtimeVersion = result.latest; } catch (err) { this.$logger.trace( @@ -529,6 +530,7 @@ export class AndroidPluginBuildService implements IAndroidPluginBuildService { `${SCOPED_ANDROID_RUNTIME_NAME}@${runtimeVersion}`, { version_info: true } ); + output = output?.["version_info"] ?? output; if (!output) { /** @@ -543,6 +545,7 @@ export class AndroidPluginBuildService implements IAndroidPluginBuildService { `${SCOPED_ANDROID_RUNTIME_NAME}@${runtimeVersion}`, { gradle: true } ); + output = output?.["gradle"] ?? output; const { version, android } = output; @@ -709,7 +712,9 @@ export class AndroidPluginBuildService implements IAndroidPluginBuildService { projectDir: pluginBuildSettings.projectDir, }); pluginBuildSettings.androidToolsInfo = this.$androidToolsInfo.getToolsInfo( - { projectDir: pluginBuildSettings.projectDir } + { + projectDir: pluginBuildSettings.projectDir, + } ); } diff --git a/lib/services/pacote-service.ts b/lib/services/pacote-service.ts index 2e07a82281..ded57a120d 100644 --- a/lib/services/pacote-service.ts +++ b/lib/services/pacote-service.ts @@ -29,7 +29,8 @@ export class PacoteService implements IPacoteService { options?: IPacoteManifestOptions ): Promise { this.$logger.trace( - `Calling pacoteService.manifest for packageName: '${packageName}' and options: ${options}` + `Calling pacoteService.manifest for packageName: '${packageName}' and options: `, + options ); const manifestOptions: IPacoteBaseOptions = await this.getPacoteBaseOptions(); @@ -39,13 +40,12 @@ export class PacoteService implements IPacoteService { packageName = this.getRealPackageName(packageName); this.$logger.trace( - `Calling pacote.manifest for packageName: ${packageName} and options: ${JSON.stringify( - manifestOptions, - null, - 2 - )}` + `Calling pacote.manifest for packageName: ${packageName} and manifestOptions:`, + manifestOptions ); - const result = pacote.manifest(packageName, manifestOptions); + const result = await pacote.manifest(packageName, manifestOptions); + + this.$logger.trace("pacote.manifest result:", result); return result; } diff --git a/lib/yarn2-package-manager.ts b/lib/yarn2-package-manager.ts new file mode 100644 index 0000000000..a8312abff3 --- /dev/null +++ b/lib/yarn2-package-manager.ts @@ -0,0 +1,168 @@ +import * as path from "path"; +import * as _ from "lodash"; +import { BasePackageManager } from "./base-package-manager"; +import { exported } from "./common/decorators"; +import { + INodePackageManagerInstallOptions, + INpmInstallResultInfo, + INpmsResult, +} from "./declarations"; +import { + IChildProcess, + IErrors, + IFileSystem, + IHostInfo, + Server, + IDictionary, +} from "./common/declarations"; +import { injector } from "./common/yok"; + +export class Yarn2PackageManager extends BasePackageManager { + private $hostInfo_: IHostInfo; + constructor( + $childProcess: IChildProcess, + private $errors: IErrors, + $fs: IFileSystem, + $hostInfo: IHostInfo, + private $httpClient: Server.IHttpClient, + private $logger: ILogger, + $pacoteService: IPacoteService + ) { + super($childProcess, $fs, $hostInfo, $pacoteService, "yarn2"); + this.$hostInfo_ = $hostInfo; + } + + protected getPackageManagerExecutableName(): string { + let executableName = "yarn"; + + if (this.$hostInfo_.isWindows) { + executableName += ".cmd"; + } + + return executableName; + } + + @exported("yarn2") + 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); + + // remove unsupported flags + // todo: refactor all package managers to map typed flags to the actual flags + const cleanedConfig = _.omit(config, ["save-dev", "save-exact"]); + + const flags = this.getFlagsString(cleanedConfig, true); + let params = []; + const isInstallingAllDependencies = packageName === pathToSave; + if (!isInstallingAllDependencies) { + params.push("add", packageName); + } + + params = params.concat(flags); + const cwd = pathToSave; + + try { + const result = await this.processPackageManagerInstall( + packageName, + params, + { cwd, isInstallingAllDependencies } + ); + return result; + } catch (e) { + this.$fs.writeJson(packageJsonPath, jsonContentBefore); + throw e; + } + } + + @exported("yarn2") + public uninstall( + packageName: string, + config?: IDictionary, + cwd?: string + ): Promise { + const flags = this.getFlagsString(config, false); + return this.$childProcess.exec(`yarn remove ${packageName} ${flags}`, { + cwd, + }); + } + + @exported("yarn2") + 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 npm info ${packageName} ${flags}` + ); + } catch (e) { + this.$errors.fail(e.message); + } + + try { + return JSON.parse(viewResult); + } catch (err) { + this.$errors.fail(err.message); + return null; + } + } + + @exported("yarn2") + 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("yarn2") + public async getRegistryPackageData(packageName: string): Promise { + const registry = await this.$childProcess.exec( + `yarn config get npmRegistryServer` + ); + 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("yarn2") + public async getCachePath(): Promise { + const result = await this.$childProcess.exec(`yarn config get cacheFolder`); + return result.toString().trim(); + } +} + +injector.register("yarn2", Yarn2PackageManager); diff --git a/test/controllers/add-platform-controller.ts b/test/controllers/add-platform-controller.ts index 8b98fe0ec3..613b7d79ef 100644 --- a/test/controllers/add-platform-controller.ts +++ b/test/controllers/add-platform-controller.ts @@ -8,6 +8,7 @@ import { AddPlaformErrors } from "../../lib/constants"; import { PackageManager } from "../../lib/package-manager"; import { NodePackageManager } from "../../lib/node-package-manager"; import { YarnPackageManager } from "../../lib/yarn-package-manager"; +import { Yarn2PackageManager } from "../../lib/yarn2-package-manager"; import { PnpmPackageManager } from "../../lib/pnpm-package-manager"; import { MobileHelper } from "../../lib/common/mobile/mobile-helper"; @@ -29,6 +30,7 @@ function createInjector(data?: { latestFrameworkVersion: string }) { injector.register("packageManager", PackageManager); injector.register("npm", NodePackageManager); injector.register("yarn", YarnPackageManager); + injector.register("yarn2", Yarn2PackageManager); injector.register("pnpm", PnpmPackageManager); injector.register("userSettingsService", { getSettingValue: async (settingName: string): Promise => undefined, diff --git a/test/package-installation-manager.ts b/test/package-installation-manager.ts index 9b9bcea58d..5704dda860 100644 --- a/test/package-installation-manager.ts +++ b/test/package-installation-manager.ts @@ -6,6 +6,7 @@ import * as HostInfoLib from "../lib/common/host-info"; import * as LoggerLib from "../lib/common/logger/logger"; import * as NpmLib from "../lib/node-package-manager"; import * as YarnLib from "../lib/yarn-package-manager"; +import * as Yarn2Lib from "../lib/yarn2-package-manager"; import * as PnpmLib from "../lib/pnpm-package-manager"; import * as PackageManagerLib from "../lib/package-manager"; import * as PackageInstallationManagerLib from "../lib/package-installation-manager"; @@ -46,6 +47,7 @@ function createTestInjector(): IInjector { }); testInjector.register("npm", NpmLib.NodePackageManager); testInjector.register("yarn", YarnLib.YarnPackageManager); + testInjector.register("yarn2", Yarn2Lib.Yarn2PackageManager); testInjector.register("pnpm", PnpmLib.PnpmPackageManager); testInjector.register("packageManager", PackageManagerLib.PackageManager); testInjector.register("projectConfigService", ProjectConfigServiceStub); diff --git a/test/plugins-service.ts b/test/plugins-service.ts index d5b74fd4ba..a531026304 100644 --- a/test/plugins-service.ts +++ b/test/plugins-service.ts @@ -4,6 +4,7 @@ import { PackageManager } from "../lib/package-manager"; import { PackageInstallationManager } from "../lib/package-installation-manager"; import { NodePackageManager } from "../lib/node-package-manager"; import { YarnPackageManager } from "../lib/yarn-package-manager"; +import { Yarn2PackageManager } from "../lib/yarn2-package-manager"; import { PnpmPackageManager } from "../lib/pnpm-package-manager"; import { ProjectData } from "../lib/project-data"; import { ChildProcess } from "../lib/common/child-process"; @@ -74,6 +75,7 @@ function createTestInjector() { ); testInjector.register("npm", NodePackageManager); testInjector.register("yarn", YarnPackageManager); + testInjector.register("yarn2", Yarn2PackageManager); testInjector.register("pnpm", PnpmPackageManager); testInjector.register("fs", FileSystem); // const fileSystemStub = new stubs.FileSystemStub(); diff --git a/test/services/extensibility-service.ts b/test/services/extensibility-service.ts index 672ff84db0..0a5067a798 100644 --- a/test/services/extensibility-service.ts +++ b/test/services/extensibility-service.ts @@ -5,6 +5,7 @@ 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 { Yarn2PackageManager } from "../../lib/yarn2-package-manager"; import { PnpmPackageManager } from "../../lib/pnpm-package-manager"; import * as constants from "../../lib/constants"; import { ChildProcess } from "../../lib/common/child-process"; @@ -75,6 +76,7 @@ describe("extensibilityService", () => { }); testInjector.register("npm", NodePackageManager); testInjector.register("yarn", YarnPackageManager); + testInjector.register("yarn2", Yarn2PackageManager); testInjector.register("pnpm", PnpmPackageManager); testInjector.register("settingsService", SettingsService); testInjector.register("requireService", {