diff --git a/lib/bootstrap.ts b/lib/bootstrap.ts index 5892901282..afe39f6245 100644 --- a/lib/bootstrap.ts +++ b/lib/bootstrap.ts @@ -217,6 +217,7 @@ 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.requirePublic("bun", "./bun-package-manager"); injector.requireCommand( "package-manager|*get", "./commands/package-manager-get" diff --git a/lib/bun-package-manager.ts b/lib/bun-package-manager.ts new file mode 100644 index 0000000000..b2d5b8f842 --- /dev/null +++ b/lib/bun-package-manager.ts @@ -0,0 +1,158 @@ +import * as path from "path"; +import { BasePackageManager } from "./base-package-manager"; +import { exported, cache } from "./common/decorators"; +import { CACACHE_DIRECTORY_NAME } from "./constants"; +import * as _ from "lodash"; +import { + INodePackageManagerInstallOptions, + INpmInstallResultInfo, + INpmsResult, +} from "./declarations"; +import { + IChildProcess, + IErrors, + IFileSystem, + IHostInfo, + Server, +} from "./common/declarations"; +import { injector } from "./common/yok"; + +export class BunPackageManager extends BasePackageManager { + constructor( + $childProcess: IChildProcess, + private $errors: IErrors, + $fs: IFileSystem, + $hostInfo: IHostInfo, + private $logger: ILogger, + private $httpClient: Server.IHttpClient, + $pacoteService: IPacoteService + ) { + super($childProcess, $fs, $hostInfo, $pacoteService, "bun"); + } + + @exported("bun") + 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); + // TODO: Confirm desired behavior. The npm version uses --legacy-peer-deps + // by default, we could use `--no-peer` for Bun if similar is needed; the + // pnpm version uses `--shamefully-hoist`, but Bun has no similar flag. + let params = ["install", "--legacy-peer-deps"]; + const isInstallingAllDependencies = packageName === pathToSave; + if (!isInstallingAllDependencies) { + params.push(packageName); + } + + params = params.concat(flags); + const cwd = pathToSave; + + try { + const result = await this.processPackageManagerInstall( + packageName, + params, + { cwd, isInstallingAllDependencies } + ); + return result; + } catch (err) { + // Revert package.json contents to preserve valid state + this.$fs.writeJson(packageJsonPath, jsonContentBefore); + throw err; + } + } + + @exported("bun") + public async uninstall( + packageName: string, + config?: any, + cwd?: string + ): Promise { + const flags = this.getFlagsString(config, false); + return this.$childProcess.exec(`bun remove ${packageName} ${flags}`, { + cwd, + }); + } + + // Bun does not have a `view` command; use npm. + @exported("bun") + public async view(packageName: string, config: Object): Promise { + const wrappedConfig = _.extend({}, config, { json: true }); // always require view response as JSON + + const flags = this.getFlagsString(wrappedConfig, false); + let viewResult: any; + try { + viewResult = await this.$childProcess.exec( + `npm view ${packageName} ${flags}` + ); + } catch (e) { + this.$errors.fail(e.message); + } + + try { + return JSON.parse(viewResult); + } catch (err) { + return null; + } + } + + // Bun does not have a `search` command; use npm. + @exported("bun") + public async search(filter: string[], config: any): Promise { + const flags = this.getFlagsString(config, false); + return this.$childProcess.exec(`npm search ${filter.join(" ")} ${flags}`); + } + + public async searchNpms(keyword: string): Promise { + // Bugs with npms.io: + // 1. API returns no results when a valid package name contains @ or / + // even if using encodeURIComponent(). + // 2. npms.io's API no longer returns updated results; see + // https://github.com/npms-io/npms-api/issues/112. Better to switch to + // https://registry.npmjs.org/ + const httpRequestResult = await this.$httpClient.httpRequest( + `https://api.npms.io/v2/search?q=keywords:${keyword}` + ); + const result: INpmsResult = JSON.parse(httpRequestResult.body); + return result; + } + + // Bun does not have a command analogous to `npm config get registry`; Bun + // uses `bunfig.toml` to define custom registries. + // - TODO: read `bunfig.toml`, if it exists, and return the registry URL. + public async getRegistryPackageData(packageName: string): Promise { + const registry = await this.$childProcess.exec(`npm config get registry`); + const url = registry.trim() + packageName; + this.$logger.trace( + `Trying to get data from npm registry for package ${packageName}, url is: ${url}` + ); + const responseData = (await this.$httpClient.httpRequest(url)).body; + this.$logger.trace( + `Successfully received data from npm registry for package ${packageName}. Response data is: ${responseData}` + ); + const jsonData = JSON.parse(responseData); + this.$logger.trace( + `Successfully parsed data from npm registry for package ${packageName}.` + ); + return jsonData; + } + + @cache() + public async getCachePath(): Promise { + const cachePath = await this.$childProcess.exec(`bun pm cache`); + return path.join(cachePath.trim(), CACACHE_DIRECTORY_NAME); + } +} + +injector.register("bun", BunPackageManager); diff --git a/lib/commands/preview.ts b/lib/commands/preview.ts index 1c3187d99f..872267b260 100644 --- a/lib/commands/preview.ts +++ b/lib/commands/preview.ts @@ -44,13 +44,11 @@ export class PreviewCommand implements ICommand { const previewCLIPath = this.getPreviewCLIPath(); if (!previewCLIPath) { - const packageManagerName = await this.$packageManager.getPackageManagerName(); + const packageManagerName = + await this.$packageManager.getPackageManagerName(); let installCommand = ""; switch (packageManagerName) { - case PackageManagers.npm: - installCommand = "npm install --save-dev @nativescript/preview-cli"; - break; case PackageManagers.yarn: case PackageManagers.yarn2: installCommand = "yarn add -D @nativescript/preview-cli"; @@ -58,6 +56,12 @@ export class PreviewCommand implements ICommand { case PackageManagers.pnpm: installCommand = "pnpm install --save-dev @nativescript/preview-cli"; break; + case PackageManagers.bun: + installCommand = "bun add --dev @nativescript/preview-cli"; + case PackageManagers.npm: + default: + installCommand = "npm install --save-dev @nativescript/preview-cli"; + break; } this.$logger.info( [ diff --git a/lib/common/dispatchers.ts b/lib/common/dispatchers.ts index 5227ee5221..1fa9edb065 100644 --- a/lib/common/dispatchers.ts +++ b/lib/common/dispatchers.ts @@ -120,9 +120,6 @@ export class CommandDispatcher implements ICommandDispatcher { let updateCommand = ""; switch (packageManagerName) { - case PackageManagers.npm: - updateCommand = "npm i -g nativescript"; - break; case PackageManagers.yarn: case PackageManagers.yarn2: updateCommand = "yarn global add nativescript"; @@ -130,6 +127,13 @@ export class CommandDispatcher implements ICommandDispatcher { case PackageManagers.pnpm: updateCommand = "pnpm i -g nativescript"; break; + case PackageManagers.bun: + updateCommand = "bun add --global nativescript"; + break; + case PackageManagers.npm: + default: + updateCommand = "npm i -g nativescript"; + break; } if ( diff --git a/lib/constants.ts b/lib/constants.ts index bb1cf94aa5..c15d9b1975 100644 --- a/lib/constants.ts +++ b/lib/constants.ts @@ -160,7 +160,8 @@ export class ITMSConstants { } class ItunesConnectApplicationTypesClass - implements IiTunesConnectApplicationType { + implements IiTunesConnectApplicationType +{ public iOS = "iOS App"; public Mac = "Mac OS X App"; } @@ -168,7 +169,8 @@ class ItunesConnectApplicationTypesClass export const iOSAppResourcesFolderName = "iOS"; export const androidAppResourcesFolderName = "Android"; -export const ItunesConnectApplicationTypes = new ItunesConnectApplicationTypesClass(); +export const ItunesConnectApplicationTypes = + new ItunesConnectApplicationTypesClass(); export const VUE_NAME = "vue"; export const ANGULAR_NAME = "angular"; export const JAVASCRIPT_NAME = "javascript"; @@ -478,4 +480,5 @@ export enum PackageManagers { pnpm = "pnpm", yarn = "yarn", yarn2 = "yarn2", + bun = "bun", } diff --git a/lib/package-manager.ts b/lib/package-manager.ts index 8944fc7148..df6d18aa92 100644 --- a/lib/package-manager.ts +++ b/lib/package-manager.ts @@ -28,6 +28,7 @@ export class PackageManager implements IPackageManager { private $yarn: INodePackageManager, private $yarn2: INodePackageManager, private $pnpm: INodePackageManager, + private $bun: INodePackageManager, private $logger: ILogger, private $userSettingsService: IUserSettingsService, private $projectConfigService: IProjectConfigService @@ -144,9 +145,8 @@ export class PackageManager implements IPackageManager { } try { - const configPm = this.$projectConfigService.getValue( - "cli.packageManager" - ); + const configPm = + this.$projectConfigService.getValue("cli.packageManager"); if (configPm) { this.$logger.trace( @@ -172,6 +172,9 @@ export class PackageManager implements IPackageManager { } else if (pm === PackageManagers.pnpm || this.$options.pnpm) { this._packageManagerName = PackageManagers.pnpm; return this.$pnpm; + } else if (pm === PackageManagers.bun) { + this._packageManagerName = PackageManagers.bun; + return this.$bun; } else { this._packageManagerName = PackageManagers.npm; return this.$npm; diff --git a/test/bun-package-manager.ts b/test/bun-package-manager.ts new file mode 100644 index 0000000000..758b620f54 --- /dev/null +++ b/test/bun-package-manager.ts @@ -0,0 +1,97 @@ +import { Yok } from "../lib/common/yok"; +import * as stubs from "./stubs"; +import { assert } from "chai"; +import { BunPackageManager } from "../lib/bun-package-manager"; +import { IInjector } from "../lib/common/definitions/yok"; + +function createTestInjector(configuration: {} = {}): IInjector { + const injector = new Yok(); + injector.register("hostInfo", {}); + injector.register("errors", stubs.ErrorsStub); + injector.register("logger", stubs.LoggerStub); + injector.register("childProcess", stubs.ChildProcessStub); + injector.register("httpClient", {}); + injector.register("fs", stubs.FileSystemStub); + injector.register("bun", BunPackageManager); + injector.register("pacoteService", { + manifest: () => Promise.resolve(), + }); + + return injector; +} + +describe("node-package-manager", () => { + describe("getPackageNameParts", () => { + [ + { + name: "should return both name and version when valid fullName passed", + templateFullName: "some-template@1.0.0", + expectedVersion: "1.0.0", + expectedName: "some-template", + }, + { + name: "should return both name and version when valid fullName with scope passed", + templateFullName: "@nativescript/some-template@1.0.0", + expectedVersion: "1.0.0", + expectedName: "@nativescript/some-template", + }, + { + name: "should return only name when version is not specified and the template is scoped", + templateFullName: "@nativescript/some-template", + expectedVersion: "", + expectedName: "@nativescript/some-template", + }, + { + name: "should return only name when version is not specified", + templateFullName: "some-template", + expectedVersion: "", + expectedName: "some-template", + }, + ].forEach((testCase) => { + it(testCase.name, async () => { + const testInjector = createTestInjector(); + const npm = testInjector.resolve("bun"); + const templateNameParts = await npm.getPackageNameParts( + testCase.templateFullName + ); + assert.strictEqual(templateNameParts.name, testCase.expectedName); + assert.strictEqual(templateNameParts.version, testCase.expectedVersion); + }); + }); + }); + + describe("getPackageFullName", () => { + [ + { + name: "should return name and version when specified", + templateName: "some-template", + templateVersion: "1.0.0", + expectedFullName: "some-template@1.0.0", + }, + { + name: "should return only the github url when no version specified", + templateName: + "https://github.com/NativeScript/template-drawer-navigation-ng#master", + templateVersion: "", + expectedFullName: + "https://github.com/NativeScript/template-drawer-navigation-ng#master", + }, + { + name: "should return only the name when no version specified", + templateName: "some-template", + templateVersion: "", + expectedFullName: "some-template", + }, + ].forEach((testCase) => { + it(testCase.name, async () => { + const testInjector = createTestInjector(); + const npm = testInjector.resolve("bun"); + const templateFullName = await npm.getPackageFullName({ + name: testCase.templateName, + version: testCase.templateVersion, + }); + assert.strictEqual(templateFullName, testCase.expectedFullName); + }); + }); + }); +}); diff --git a/test/controllers/add-platform-controller.ts b/test/controllers/add-platform-controller.ts index 613b7d79ef..cc2b9e62ae 100644 --- a/test/controllers/add-platform-controller.ts +++ b/test/controllers/add-platform-controller.ts @@ -10,6 +10,7 @@ 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 { BunPackageManager } from "../../lib/bun-package-manager"; import { MobileHelper } from "../../lib/common/mobile/mobile-helper"; let actualMessage: string = null; @@ -32,6 +33,8 @@ function createInjector(data?: { latestFrameworkVersion: string }) { injector.register("yarn", YarnPackageManager); injector.register("yarn2", Yarn2PackageManager); injector.register("pnpm", PnpmPackageManager); + injector.register("bun", BunPackageManager); + injector.register("userSettingsService", { getSettingValue: async (settingName: string): Promise => undefined, }); @@ -69,8 +72,7 @@ describe("PlatformController", () => { latestFrameworkVersion: "7.0.0", }, { - name: - "should add the latest compatible version (tns platform add )", + name: "should add the latest compatible version (tns platform add )", latestFrameworkVersion, getPlatformParam: (platform: string) => `${platform}@${latestFrameworkVersion}`, @@ -96,9 +98,8 @@ describe("PlatformController", () => { const platformParam = testCase.getPlatformParam ? testCase.getPlatformParam(platform) : platform; - const platformController: PlatformController = injector.resolve( - "platformController" - ); + const platformController: PlatformController = + injector.resolve("platformController"); await platformController.addPlatform({ projectDir, platform: platformParam, @@ -123,9 +124,8 @@ describe("PlatformController", () => { const fs = injector.resolve("fs"); fs.exists = (filePath: string) => filePath !== frameworkPath; - const platformController: PlatformController = injector.resolve( - "platformController" - ); + const platformController: PlatformController = + injector.resolve("platformController"); await assert.isRejected( platformController.addPlatform({ projectDir, platform, frameworkPath }), @@ -175,9 +175,8 @@ describe("PlatformController", () => { } }; - const platformController: PlatformController = injector.resolve( - "platformController" - ); + const platformController: PlatformController = + injector.resolve("platformController"); await platformController.addPlatform({ projectDir, platform: "android", diff --git a/test/package-installation-manager.ts b/test/package-installation-manager.ts index 8fcbac47c1..ca2cbe409e 100644 --- a/test/package-installation-manager.ts +++ b/test/package-installation-manager.ts @@ -8,6 +8,7 @@ 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 BunLib from "../lib/bun-package-manager"; import * as PackageManagerLib from "../lib/package-manager"; import * as PackageInstallationManagerLib from "../lib/package-installation-manager"; import * as OptionsLib from "../lib/options"; @@ -49,6 +50,7 @@ function createTestInjector(): IInjector { testInjector.register("yarn", YarnLib.YarnPackageManager); testInjector.register("yarn2", Yarn2Lib.Yarn2PackageManager); testInjector.register("pnpm", PnpmLib.PnpmPackageManager); + testInjector.register("bun", BunLib.BunPackageManager); testInjector.register("packageManager", PackageManagerLib.PackageManager); testInjector.register("projectConfigService", ProjectConfigServiceStub); testInjector.register( @@ -114,130 +116,148 @@ describe("Npm installation manager tests", () => { expectedResult: "1.4.0", }, - "when there's only one available version and it is higher than match CLI's version": { - versions: ["1.4.0"], - packageLatestVersion: "1.4.0", - cliVersion: "1.2.0", - expectedResult: "1.4.0", - }, + "when there's only one available version and it is higher than match CLI's version": + { + versions: ["1.4.0"], + packageLatestVersion: "1.4.0", + cliVersion: "1.2.0", + expectedResult: "1.4.0", + }, - "when there's only one available version and it is lower than CLI's version": { - versions: ["1.4.0"], - packageLatestVersion: "1.4.0", - cliVersion: "1.6.0", - expectedResult: "1.4.0", - }, + "when there's only one available version and it is lower than CLI's version": + { + versions: ["1.4.0"], + packageLatestVersion: "1.4.0", + cliVersion: "1.6.0", + expectedResult: "1.4.0", + }, - "when there are multiple package versions and the latest one matches ~": { - versions: ["1.2.0", "1.3.0", "1.3.1", "1.3.2", "1.3.3", "1.4.0"], - packageLatestVersion: "1.3.3", - cliVersion: "1.3.0", - expectedResult: "1.3.3", - }, + "when there are multiple package versions and the latest one matches ~": + { + versions: ["1.2.0", "1.3.0", "1.3.1", "1.3.2", "1.3.3", "1.4.0"], + packageLatestVersion: "1.3.3", + cliVersion: "1.3.0", + expectedResult: "1.3.3", + }, - "when there are multiple package versions and the latest one matches ~ when there are newer matching versions but they are not under latest tag": { - versions: ["1.2.0", "1.3.0", "1.3.1", "1.3.2", "1.3.3", "1.4.0"], - packageLatestVersion: "1.3.2", - cliVersion: "1.3.0", - expectedResult: "1.3.2", - }, + "when there are multiple package versions and the latest one matches ~ when there are newer matching versions but they are not under latest tag": + { + versions: ["1.2.0", "1.3.0", "1.3.1", "1.3.2", "1.3.3", "1.4.0"], + packageLatestVersion: "1.3.2", + cliVersion: "1.3.0", + expectedResult: "1.3.2", + }, - "when there are multiple package versions and the latest one is lower than ~": { - versions: ["1.2.0", "1.3.0", "1.3.1", "1.3.2", "1.3.3", "1.4.0"], - packageLatestVersion: "1.4.0", - cliVersion: "1.5.0", - expectedResult: "1.4.0", - }, + "when there are multiple package versions and the latest one is lower than ~": + { + versions: ["1.2.0", "1.3.0", "1.3.1", "1.3.2", "1.3.3", "1.4.0"], + packageLatestVersion: "1.4.0", + cliVersion: "1.5.0", + expectedResult: "1.4.0", + }, - "when there are multiple package versions and there's beta version matching CLI's semver": { - versions: ["1.2.0", "1.3.0", "1.3.1", "1.4.0", "1.5.0-2016-02-25-182"], - packageLatestVersion: "1.4.0", - cliVersion: "1.5.0", - expectedResult: "1.4.0", - }, + "when there are multiple package versions and there's beta version matching CLI's semver": + { + versions: [ + "1.2.0", + "1.3.0", + "1.3.1", + "1.4.0", + "1.5.0-2016-02-25-182", + ], + packageLatestVersion: "1.4.0", + cliVersion: "1.5.0", + expectedResult: "1.4.0", + }, - "when there are multiple package versions and package's latest version is greater than CLI's version": { - versions: [ - "1.2.0", - "1.3.0", - "1.3.1", - "1.4.0", - "1.5.0-2016-02-25-182", - "1.5.0", - "1.6.0", - ], - packageLatestVersion: "1.6.0", - cliVersion: "1.5.0", - expectedResult: "1.5.0", - }, + "when there are multiple package versions and package's latest version is greater than CLI's version": + { + versions: [ + "1.2.0", + "1.3.0", + "1.3.1", + "1.4.0", + "1.5.0-2016-02-25-182", + "1.5.0", + "1.6.0", + ], + packageLatestVersion: "1.6.0", + cliVersion: "1.5.0", + expectedResult: "1.5.0", + }, - "when there are multiple versions latest one does not match CLI's semver and other versions are not matching either": { - versions: [ - "1.0.0", - "1.0.1", - "1.2.0", - "1.3.1", - "1.4.0", - "1.5.0-2016-02-25-182", - "1.5.0", - ], - packageLatestVersion: "1.0.0", - cliVersion: "1.1.0", - expectedResult: "1.0.0", - }, + "when there are multiple versions latest one does not match CLI's semver and other versions are not matching either": + { + versions: [ + "1.0.0", + "1.0.1", + "1.2.0", + "1.3.1", + "1.4.0", + "1.5.0-2016-02-25-182", + "1.5.0", + ], + packageLatestVersion: "1.0.0", + cliVersion: "1.1.0", + expectedResult: "1.0.0", + }, - "when CLI's version is beta (has dash) latest matching beta version is returned": { - versions: [ - "1.0.0", - "1.0.1", - "1.4.0", - "1.5.0-2016-02-25-182", - "1.5.0-2016-02-26-202", - ], - packageLatestVersion: "1.4.0", - cliVersion: "1.5.0-182", - expectedResult: "1.5.0-2016-02-26-202", - }, + "when CLI's version is beta (has dash) latest matching beta version is returned": + { + versions: [ + "1.0.0", + "1.0.1", + "1.4.0", + "1.5.0-2016-02-25-182", + "1.5.0-2016-02-26-202", + ], + packageLatestVersion: "1.4.0", + cliVersion: "1.5.0-182", + expectedResult: "1.5.0-2016-02-26-202", + }, - "when CLI's version is beta (has dash) latest matching official version is returned when beta versions do not match": { - versions: [ - "1.0.0", - "1.0.1", - "1.4.0", - "1.5.0-2016-02-25-182", - "1.5.0-2016-02-26-202", - ], - packageLatestVersion: "1.4.0", - cliVersion: "1.6.0-2016-03-01-182", - expectedResult: "1.4.0", - }, + "when CLI's version is beta (has dash) latest matching official version is returned when beta versions do not match": + { + versions: [ + "1.0.0", + "1.0.1", + "1.4.0", + "1.5.0-2016-02-25-182", + "1.5.0-2016-02-26-202", + ], + packageLatestVersion: "1.4.0", + cliVersion: "1.6.0-2016-03-01-182", + expectedResult: "1.4.0", + }, - "when CLI's version is beta (has dash) latest matching official version is returned when beta versions do not match (when the prerelease of CLI is higher than prerelease version of runtime)": { - versions: [ - "1.0.0", - "1.0.1", - "1.4.0", - "1.6.0-2016-02-25-182", - "1.6.0-2016-02-26-202", - ], - packageLatestVersion: "1.4.0", - cliVersion: "1.6.0-2016-10-01-182", - expectedResult: "1.4.0", - }, - "When CLI Version has patch version larger than an existing package, should return max compliant package from the same major.minor version": { - versions: [ - "1.0.0", - "1.0.1", - "1.4.0", - "2.5.0", - "2.5.1", - "2.5.2", - "3.0.0", - ], - packageLatestVersion: "3.0.0", - cliVersion: "2.5.4", - expectedResult: "2.5.2", - }, + "when CLI's version is beta (has dash) latest matching official version is returned when beta versions do not match (when the prerelease of CLI is higher than prerelease version of runtime)": + { + versions: [ + "1.0.0", + "1.0.1", + "1.4.0", + "1.6.0-2016-02-25-182", + "1.6.0-2016-02-26-202", + ], + packageLatestVersion: "1.4.0", + cliVersion: "1.6.0-2016-10-01-182", + expectedResult: "1.4.0", + }, + "When CLI Version has patch version larger than an existing package, should return max compliant package from the same major.minor version": + { + versions: [ + "1.0.0", + "1.0.1", + "1.4.0", + "2.5.0", + "2.5.1", + "2.5.2", + "3.0.0", + ], + packageLatestVersion: "3.0.0", + cliVersion: "2.5.4", + expectedResult: "2.5.2", + }, "When reference version is specified as argument": { versions: ["122.0.4", "123.0.0", "123.0.1", "123.1.0", "124.0.0"], packageLatestVersion: "124.0.0", @@ -268,10 +288,11 @@ describe("Npm installation manager tests", () => { packageInstallationManager.getLatestVersion = (packageName: string) => Promise.resolve(currentTestData.packageLatestVersion); - const actualLatestCompatibleVersion = await packageInstallationManager.getLatestCompatibleVersion( - "", - currentTestData.referenceVersion - ); + const actualLatestCompatibleVersion = + await packageInstallationManager.getLatestCompatibleVersion( + "", + currentTestData.referenceVersion + ); assert.equal( actualLatestCompatibleVersion, currentTestData.expectedResult diff --git a/test/plugins-service.ts b/test/plugins-service.ts index b2a3f51250..99d3aede71 100644 --- a/test/plugins-service.ts +++ b/test/plugins-service.ts @@ -6,6 +6,7 @@ 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 { BunPackageManager } from "../lib/bun-package-manager"; import { ProjectData } from "../lib/project-data"; import { ChildProcess } from "../lib/common/child-process"; import { Options } from "../lib/options"; @@ -76,6 +77,7 @@ function createTestInjector() { testInjector.register("yarn", YarnPackageManager); testInjector.register("yarn2", Yarn2PackageManager); testInjector.register("pnpm", PnpmPackageManager); + testInjector.register("bun", BunPackageManager); testInjector.register("fs", FileSystem); // const fileSystemStub = new stubs.FileSystemStub(); // fileSystemStub.exists = (fileName: string) => { diff --git a/test/services/extensibility-service.ts b/test/services/extensibility-service.ts index 0a5067a798..1a4e0a424f 100644 --- a/test/services/extensibility-service.ts +++ b/test/services/extensibility-service.ts @@ -7,6 +7,7 @@ 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 { BunPackageManager } from "../../lib/bun-package-manager"; import * as constants from "../../lib/constants"; import { ChildProcess } from "../../lib/common/child-process"; import { CommandsDelimiters } from "../../lib/common/constants"; @@ -78,6 +79,7 @@ describe("extensibilityService", () => { testInjector.register("yarn", YarnPackageManager); testInjector.register("yarn2", Yarn2PackageManager); testInjector.register("pnpm", PnpmPackageManager); + testInjector.register("bun", BunPackageManager); testInjector.register("settingsService", SettingsService); testInjector.register("requireService", { require: (pathToRequire: string): any => undefined, @@ -89,9 +91,8 @@ describe("extensibilityService", () => { testInjector: IInjector, extensionName: string ): string => { - const settingsService = testInjector.resolve( - "settingsService" - ); + const settingsService = + testInjector.resolve("settingsService"); const profileDir = settingsService.getProfileDir(); return path.join(profileDir, "extensions", "node_modules", extensionName); @@ -134,9 +135,8 @@ describe("extensibilityService", () => { throw new Error(expectedErrorMessage); }; - const extensibilityService: IExtensibilityService = testInjector.resolve( - ExtensibilityService - ); + const extensibilityService: IExtensibilityService = + testInjector.resolve(ExtensibilityService); await assert.isRejected( extensibilityService.installExtension("extensionToInstall"), expectedErrorMessage @@ -153,9 +153,8 @@ describe("extensibilityService", () => { throw new Error(expectedErrorMessage); }; - const extensibilityService: IExtensibilityService = testInjector.resolve( - ExtensibilityService - ); + const extensibilityService: IExtensibilityService = + testInjector.resolve(ExtensibilityService); await assert.isRejected( extensibilityService.installExtension("extensionToInstall"), expectedErrorMessage @@ -176,9 +175,8 @@ describe("extensibilityService", () => { throw new Error(expectedErrorMessage); }; - const extensibilityService: IExtensibilityService = testInjector.resolve( - ExtensibilityService - ); + const extensibilityService: IExtensibilityService = + testInjector.resolve(ExtensibilityService); await assert.isRejected( extensibilityService.installExtension("extensionToInstall"), expectedErrorMessage @@ -210,9 +208,8 @@ describe("extensibilityService", () => { return { name: userSpecifiedValue }; }; - const extensibilityService: IExtensibilityService = testInjector.resolve( - ExtensibilityService - ); + const extensibilityService: IExtensibilityService = + testInjector.resolve(ExtensibilityService); await extensibilityService.installExtension(userSpecifiedValue); return argsPassedToNpmInstall; @@ -222,9 +219,10 @@ describe("extensibilityService", () => { userSpecifiedValue: string, expectedValue: string ): Promise => { - const argsPassedToNpmInstall = await getArgsPassedToNpmInstallDuringInstallExtensionCall( - userSpecifiedValue - ); + const argsPassedToNpmInstall = + await getArgsPassedToNpmInstallDuringInstallExtensionCall( + userSpecifiedValue + ); assert.deepStrictEqual( argsPassedToNpmInstall.packageName, expectedValue @@ -249,9 +247,10 @@ describe("extensibilityService", () => { it("passes save and save-exact options to npm install", async () => { const extensionName = "extension1"; - const argsPassedToNpmInstall = await getArgsPassedToNpmInstallDuringInstallExtensionCall( - extensionName - ); + const argsPassedToNpmInstall = + await getArgsPassedToNpmInstallDuringInstallExtensionCall( + extensionName + ); const expectedNpmConfg: any = { save: true }; expectedNpmConfg["save-exact"] = true; assert.deepStrictEqual(argsPassedToNpmInstall.config, expectedNpmConfg); @@ -260,17 +259,17 @@ describe("extensibilityService", () => { it("passes full path to extensions dir for installation", async () => { const extensionName = "extension1"; const testInjector = getTestInjector(); - const settingsService: ISettingsService = testInjector.resolve( - "settingsService" - ); + const settingsService: ISettingsService = + testInjector.resolve("settingsService"); const profileDir = "my-profile-dir"; settingsService.getProfileDir = () => profileDir; const expectedDirForInstallation = path.join(profileDir, "extensions"); - const argsPassedToNpmInstall = await getArgsPassedToNpmInstallDuringInstallExtensionCall( - extensionName, - testInjector - ); + const argsPassedToNpmInstall = + await getArgsPassedToNpmInstallDuringInstallExtensionCall( + extensionName, + testInjector + ); assert.deepStrictEqual( argsPassedToNpmInstall.pathToSave, expectedDirForInstallation @@ -297,9 +296,8 @@ describe("extensibilityService", () => { config?: any ): Promise => ({ name: extensionName, version: "1.0.0" }); - const extensibilityService: IExtensibilityService = testInjector.resolve( - ExtensibilityService - ); + const extensibilityService: IExtensibilityService = + testInjector.resolve(ExtensibilityService); const actualResult = await extensibilityService.installExtension( extensionName ); @@ -346,9 +344,8 @@ describe("extensibilityService", () => { }) ); - const extensibilityService: IExtensibilityService = testInjector.resolve( - ExtensibilityService - ); + const extensibilityService: IExtensibilityService = + testInjector.resolve(ExtensibilityService); const actualResult = await Promise.all( extensibilityService.loadExtensions() ); @@ -404,9 +401,8 @@ describe("extensibilityService", () => { }) ); - const extensibilityService: IExtensibilityService = testInjector.resolve( - ExtensibilityService - ); + const extensibilityService: IExtensibilityService = + testInjector.resolve(ExtensibilityService); const actualResult = await Promise.all( extensibilityService.loadExtensions() ); @@ -430,9 +426,8 @@ describe("extensibilityService", () => { mockFsReadJson(testInjector, extensionNames); - const requireService: IRequireService = testInjector.resolve( - "requireService" - ); + const requireService: IRequireService = + testInjector.resolve("requireService"); requireService.require = (module: string) => { if (path.basename(module) === extensionNames[0]) { throw new Error("Unable to load module."); @@ -454,9 +449,8 @@ describe("extensibilityService", () => { expectedResults[0] = new Error( "Unable to load extension extension1. You will not be able to use the functionality that it adds. Error: Unable to load module." ); - const extensibilityService: IExtensibilityService = testInjector.resolve( - ExtensibilityService - ); + const extensibilityService: IExtensibilityService = + testInjector.resolve(ExtensibilityService); const promises = extensibilityService.loadExtensions(); assert.deepStrictEqual(promises.length, extensionNames.length); @@ -495,9 +489,8 @@ describe("extensibilityService", () => { throw new Error(expectedErrorMessage); }; - const extensibilityService: IExtensibilityService = testInjector.resolve( - ExtensibilityService - ); + const extensibilityService: IExtensibilityService = + testInjector.resolve(ExtensibilityService); const promises = extensibilityService.loadExtensions(); for (let index = 0; index < promises.length; index++) { @@ -561,9 +554,8 @@ describe("extensibilityService", () => { throw new Error(expectedErrorMessage); }; - const extensibilityService: IExtensibilityService = testInjector.resolve( - ExtensibilityService - ); + const extensibilityService: IExtensibilityService = + testInjector.resolve(ExtensibilityService); const promises = extensibilityService.loadExtensions(); for (let index = 0; index < promises.length; index++) { @@ -604,9 +596,8 @@ describe("extensibilityService", () => { throw new Error(expectedErrorMessage); }; - const extensibilityService: IExtensibilityService = testInjector.resolve( - ExtensibilityService - ); + const extensibilityService: IExtensibilityService = + testInjector.resolve(ExtensibilityService); const promises = extensibilityService.loadExtensions(); assert.deepStrictEqual(promises.length, 0); @@ -621,9 +612,8 @@ describe("extensibilityService", () => { throw new Error(expectedErrorMessage); }; - const extensibilityService: IExtensibilityService = testInjector.resolve( - ExtensibilityService - ); + const extensibilityService: IExtensibilityService = + testInjector.resolve(ExtensibilityService); const promises = extensibilityService.loadExtensions(); assert.deepStrictEqual(promises.length, 0); @@ -642,9 +632,8 @@ describe("extensibilityService", () => { }; const expectedResults: IExtensionData[] = []; - const extensibilityService: IExtensibilityService = testInjector.resolve( - ExtensibilityService - ); + const extensibilityService: IExtensibilityService = + testInjector.resolve(ExtensibilityService); const actualResult = await Promise.all( extensibilityService.loadExtensions() ); @@ -672,9 +661,8 @@ describe("extensibilityService", () => { }; const expectedResults: IExtensionData[] = []; - const extensibilityService: IExtensibilityService = testInjector.resolve( - ExtensibilityService - ); + const extensibilityService: IExtensibilityService = + testInjector.resolve(ExtensibilityService); const actualResult = await Promise.all( extensibilityService.loadExtensions() ); @@ -698,9 +686,8 @@ describe("extensibilityService", () => { throw new Error(expectedErrorMessage); }; - const extensibilityService: IExtensibilityService = testInjector.resolve( - ExtensibilityService - ); + const extensibilityService: IExtensibilityService = + testInjector.resolve(ExtensibilityService); await assert.isRejected( extensibilityService.uninstallExtension("extensionToInstall"), expectedErrorMessage @@ -717,9 +704,8 @@ describe("extensibilityService", () => { throw new Error(expectedErrorMessage); }; - const extensibilityService: IExtensibilityService = testInjector.resolve( - ExtensibilityService - ); + const extensibilityService: IExtensibilityService = + testInjector.resolve(ExtensibilityService); await assert.isRejected( extensibilityService.uninstallExtension("extensionToInstall"), expectedErrorMessage @@ -740,9 +726,8 @@ describe("extensibilityService", () => { throw new Error(expectedErrorMessage); }; - const extensibilityService: IExtensibilityService = testInjector.resolve( - ExtensibilityService - ); + const extensibilityService: IExtensibilityService = + testInjector.resolve(ExtensibilityService); await assert.isRejected( extensibilityService.uninstallExtension("extensionToInstall"), expectedErrorMessage @@ -774,9 +759,8 @@ describe("extensibilityService", () => { return [userSpecifiedValue]; }; - const extensibilityService: IExtensibilityService = testInjector.resolve( - ExtensibilityService - ); + const extensibilityService: IExtensibilityService = + testInjector.resolve(ExtensibilityService); await extensibilityService.uninstallExtension(userSpecifiedValue); return argsPassedToNpmInstall; @@ -786,9 +770,10 @@ describe("extensibilityService", () => { userSpecifiedValue: string, expectedValue: string ): Promise => { - const argsPassedToNpmInstall = await getArgsPassedToNpmUninstallDuringUninstallExtensionCall( - userSpecifiedValue - ); + const argsPassedToNpmInstall = + await getArgsPassedToNpmUninstallDuringUninstallExtensionCall( + userSpecifiedValue + ); assert.deepStrictEqual( argsPassedToNpmInstall.packageName, expectedValue @@ -811,9 +796,10 @@ describe("extensibilityService", () => { it("passes save option to npm uninstall", async () => { const extensionName = "extension1"; - const argsPassedToNpmUninstall = await getArgsPassedToNpmUninstallDuringUninstallExtensionCall( - extensionName - ); + const argsPassedToNpmUninstall = + await getArgsPassedToNpmUninstallDuringUninstallExtensionCall( + extensionName + ); const expectedNpmConfg: any = { save: true }; assert.deepStrictEqual( argsPassedToNpmUninstall.config, @@ -824,17 +810,17 @@ describe("extensibilityService", () => { it("passes full path to extensions dir for uninstallation", async () => { const extensionName = "extension1"; const testInjector = getTestInjector(); - const settingsService: ISettingsService = testInjector.resolve( - "settingsService" - ); + const settingsService: ISettingsService = + testInjector.resolve("settingsService"); const profileDir = "my-profile-dir"; settingsService.getProfileDir = () => profileDir; const expectedDirForUninstall = path.join(profileDir, "extensions"); - const argsPassedToNpmUninstall = await getArgsPassedToNpmUninstallDuringUninstallExtensionCall( - extensionName, - testInjector - ); + const argsPassedToNpmUninstall = + await getArgsPassedToNpmUninstallDuringUninstallExtensionCall( + extensionName, + testInjector + ); assert.deepStrictEqual( argsPassedToNpmUninstall.pathToSave, expectedDirForUninstall @@ -858,9 +844,8 @@ describe("extensibilityService", () => { p?: string ): Promise => [extensionName]; - const extensibilityService: IExtensibilityService = testInjector.resolve( - ExtensibilityService - ); + const extensibilityService: IExtensibilityService = + testInjector.resolve(ExtensibilityService); await extensibilityService.uninstallExtension(extensionName); }); }); @@ -875,9 +860,8 @@ describe("extensibilityService", () => { throw new Error(expectedErrorMessage); }; - const extensibilityService: IExtensibilityService = testInjector.resolve( - ExtensibilityService - ); + const extensibilityService: IExtensibilityService = + testInjector.resolve(ExtensibilityService); assert.throws( () => extensibilityService.getInstalledExtensions(), expectedErrorMessage @@ -889,9 +873,8 @@ describe("extensibilityService", () => { const fs: IFileSystem = testInjector.resolve("fs"); fs.exists = (pathToCheck: string) => false; - const extensibilityService: IExtensibilityService = testInjector.resolve( - ExtensibilityService - ); + const extensibilityService: IExtensibilityService = + testInjector.resolve(ExtensibilityService); assert.isNull(extensibilityService.getInstalledExtensions()); }); @@ -903,9 +886,8 @@ describe("extensibilityService", () => { return {}; }; - const extensibilityService: IExtensibilityService = testInjector.resolve( - ExtensibilityService - ); + const extensibilityService: IExtensibilityService = + testInjector.resolve(ExtensibilityService); assert.isUndefined(extensibilityService.getInstalledExtensions()); }); @@ -923,9 +905,8 @@ describe("extensibilityService", () => { return { dependencies }; }; - const extensibilityService: IExtensibilityService = testInjector.resolve( - ExtensibilityService - ); + const extensibilityService: IExtensibilityService = + testInjector.resolve(ExtensibilityService); assert.deepStrictEqual( extensibilityService.getInstalledExtensions(), dependencies @@ -952,16 +933,14 @@ describe("extensibilityService", () => { config?: any ): Promise => ({ name: extensionName }); - const requireService: IRequireService = testInjector.resolve( - "requireService" - ); + const requireService: IRequireService = + testInjector.resolve("requireService"); requireService.require = (pathToRequire: string) => { throw new Error(expectedErrorMessage); }; - const extensibilityService: IExtensibilityService = testInjector.resolve( - ExtensibilityService - ); + const extensibilityService: IExtensibilityService = + testInjector.resolve(ExtensibilityService); let isErrorRaised = false; try { await extensibilityService.loadExtension(extensionName); @@ -1015,8 +994,7 @@ describe("extensibilityService", () => { }, }, { - name: - "returns correct data when user enters exact hierarchical command name", + name: "returns correct data when user enters exact hierarchical command name", inputStrings: ["hierarchical", "command"], extensionsDefinitions: [ { @@ -1059,8 +1037,7 @@ describe("extensibilityService", () => { expectedResult: null, }, { - name: - "returns correct data when user enters hierarchical command and args for this command", + name: "returns correct data when user enters hierarchical command and args for this command", inputStrings: ["valid", "command", "with", "lots", "of", "params"], extensionsDefinitions: [ { @@ -1089,8 +1066,7 @@ describe("extensibilityService", () => { }, }, { - name: - "returns correct data when user enters the default value of hierarchical command", + name: "returns correct data when user enters the default value of hierarchical command", inputStrings: ["valid", "and", "lots", "of", "params"], extensionsDefinitions: [ { @@ -1105,8 +1081,7 @@ describe("extensibilityService", () => { }, }, { - name: - "returns correct data when user enters the full default value of hierarchical command", + name: "returns correct data when user enters the full default value of hierarchical command", inputStrings: ["valid", "command", "and", "lots", "of", "params"], extensionsDefinitions: [ { @@ -1121,8 +1096,7 @@ describe("extensibilityService", () => { }, }, { - name: - "returns correct data when user enters the default value of multilevel hierarchical command", + name: "returns correct data when user enters the default value of multilevel hierarchical command", inputStrings: [ "valid", "multilevel", @@ -1151,8 +1125,7 @@ describe("extensibilityService", () => { }, }, { - name: - "returns correct data when user enters the full default value of multilevel hierarchical command", + name: "returns correct data when user enters the full default value of multilevel hierarchical command", inputStrings: [ "valid", "multilevel", @@ -1259,9 +1232,8 @@ describe("extensibilityService", () => { return result; }; - const extensibilityService = testInjector.resolve< - IExtensibilityService - >(ExtensibilityService); + const extensibilityService = + testInjector.resolve(ExtensibilityService); const inputData = { inputStrings: testCase.inputStrings, commandDelimiter: CommandsDelimiters.HierarchicalCommand, @@ -1269,9 +1241,10 @@ describe("extensibilityService", () => { CommandsDelimiters.DefaultHierarchicalCommand, }; - const actualExtensionName = await extensibilityService.getExtensionNameWhereCommandIsRegistered( - inputData - ); + const actualExtensionName = + await extensibilityService.getExtensionNameWhereCommandIsRegistered( + inputData + ); assert.deepStrictEqual(actualExtensionName, testCase.expectedResult); }); }); @@ -1292,12 +1265,12 @@ describe("extensibilityService", () => { isGetRegistryPackageDataCalled = true; }; - const extensibilityService = testInjector.resolve( - ExtensibilityService - ); - const actualExtensionName = await extensibilityService.getExtensionNameWhereCommandIsRegistered( - null - ); + const extensibilityService = + testInjector.resolve(ExtensibilityService); + const actualExtensionName = + await extensibilityService.getExtensionNameWhereCommandIsRegistered( + null + ); assert.deepStrictEqual(actualExtensionName, null); assert.isFalse( isGetRegistryPackageDataCalled,