diff --git a/lib/bootstrap.ts b/lib/bootstrap.ts index a29826aa35..542d93b0f6 100644 --- a/lib/bootstrap.ts +++ b/lib/bootstrap.ts @@ -215,10 +215,10 @@ injector.require( injector.requireCommand("setup|*", "./commands/setup"); -injector.requirePublic("packageManager", "./package-manager"); -injector.requirePublic("npm", "./node-package-manager"); -injector.requirePublic("yarn", "./yarn-package-manager"); -injector.requirePublic("pnpm", "./pnpm-package-manager"); +injector.requirePublic("packageManager", "./package-managers/index"); +injector.requirePublic("npm", "./package-managers/npm"); +injector.requirePublic("pnpm", "./package-managers/pnpm"); +injector.requirePublic("yarn", "./package-managers/yarn"); injector.requireCommand( "package-manager|*get", "./commands/package-manager-get" @@ -230,7 +230,7 @@ injector.requireCommand( injector.require( "packageInstallationManager", - "./package-installation-manager" + "./package-managers/package-installation-manager" ); injector.require("deviceLogProvider", "./common/mobile/device-log-provider"); diff --git a/lib/common/mobile/application-manager-base.ts b/lib/common/mobile/application-manager-base.ts index d5daf3a7ce..040e67118f 100644 --- a/lib/common/mobile/application-manager-base.ts +++ b/lib/common/mobile/application-manager-base.ts @@ -122,9 +122,7 @@ export abstract class ApplicationManagerBase appIdentifier?: string, buildData?: IBuildData ): Promise; - public abstract uninstallApplication( - appIdentifier: string - ): Promise; + public abstract uninstallApplication(appIdentifier: string): Promise; public abstract startApplication( appData: Mobile.IApplicationData ): Promise; diff --git a/lib/common/mobile/ios/ios-device-base.ts b/lib/common/mobile/ios/ios-device-base.ts index 83b2ab56b5..4828d3230d 100644 --- a/lib/common/mobile/ios/ios-device-base.ts +++ b/lib/common/mobile/ios/ios-device-base.ts @@ -57,9 +57,7 @@ export abstract class IOSDeviceBase implements Mobile.IiOSDevice { }, `ios-debug-socket-${this.deviceInfo.identifier}-${appId}.lock`); } - protected abstract getDebugSocketCore( - appId: string - ): Promise; + protected abstract getDebugSocketCore(appId: string): Promise; protected async attachToDebuggerFoundEvent( appId: string, diff --git a/lib/common/mobile/ios/simulator/ios-simulator-device.ts b/lib/common/mobile/ios/simulator/ios-simulator-device.ts index 6654428a6b..4d45925565 100644 --- a/lib/common/mobile/ios/simulator/ios-simulator-device.ts +++ b/lib/common/mobile/ios/simulator/ios-simulator-device.ts @@ -99,7 +99,7 @@ export class IOSSimulator extends IOSDeviceBase implements Mobile.IiOSDevice { .catch((e) => this.$logger.error(e)); }, 5e3); - // the internal retry-mechanism of getDebuggerPort will ensure the above + // the internal retry-mechanism of getDebuggerPort will ensure the above // interval has a chance to execute multiple times const port = await super.getDebuggerPort(appId).finally(() => { clearInterval(postNotificationRetryInterval); diff --git a/lib/common/services/hooks-service.ts b/lib/common/services/hooks-service.ts index 043e60b6f7..c6735ce09e 100644 --- a/lib/common/services/hooks-service.ts +++ b/lib/common/services/hooks-service.ts @@ -304,8 +304,10 @@ export class HooksService implements IHooksService { private getCustomHooksByName(hookName: string): IHook[] { const hooks: IHook[] = []; - const customHooks: INsConfigHooks[] = - this.$projectConfigService.getValue("hooks", []); + const customHooks: INsConfigHooks[] = this.$projectConfigService.getValue( + "hooks", + [] + ); for (const cHook of customHooks) { if (cHook.type === hookName) { diff --git a/lib/options.ts b/lib/options.ts index f743f375e7..06465da778 100644 --- a/lib/options.ts +++ b/lib/options.ts @@ -1,6 +1,6 @@ import * as helpers from "./common/helpers"; -import * as yargs from 'yargs'; -import { hideBin } from 'yargs/helpers'; +import * as yargs from "yargs"; +import { hideBin } from "yargs/helpers"; import * as _ from "lodash"; import { IDictionary, diff --git a/lib/base-package-manager.ts b/lib/package-managers/base-package-manager.ts similarity index 97% rename from lib/base-package-manager.ts rename to lib/package-managers/base-package-manager.ts index 5c4dbbf9ad..f8815b67fb 100644 --- a/lib/base-package-manager.ts +++ b/lib/package-managers/base-package-manager.ts @@ -1,17 +1,17 @@ -import { isInteractive } from "./common/helpers"; +import { isInteractive } from "../common/helpers"; import { INodePackageManager, INodePackageManagerInstallOptions, INpmInstallResultInfo, INpmsResult, INpmPackageNameParts, -} from "./declarations"; +} from "../declarations"; import { IDictionary, IChildProcess, IFileSystem, IHostInfo, -} from "./common/declarations"; +} from "../common/declarations"; export abstract class BasePackageManager implements INodePackageManager { public abstract install( diff --git a/lib/package-managers/declarations.d.ts b/lib/package-managers/declarations.d.ts new file mode 100644 index 0000000000..fd3ff8fdfc --- /dev/null +++ b/lib/package-managers/declarations.d.ts @@ -0,0 +1,75 @@ +// This is a WIP declaration file and it is not being used yet + +interface IPackageJSON { + name?: string; + version?: string; + dependencies?: any; + devDependencies?: any; + + [key: string]: any; +} + +interface IPackageInfo { + name: string; + version: string; +} + +/** + * A package identifier can be any of the following: + * - + * - @ + * - @ + * - @ + * - + * - + * - + * - + */ +type IPackageIdentifier = string; + +interface IPackageManagerOptions { + projectPath?: string; +} +interface IPackageManagerAddOptions extends IPackageManagerOptions { + version?: string; + exact?: boolean; + scripts?: boolean; + + // these 3 are shared with the remove options, however + // they are duplicated as they don't quite make sense + // to be added to IPackageManagerOptions as they are + // specific to add and remove operations + save?: boolean; + dev?: boolean; + optional?: boolean; +} +interface IPackageManagerRemoveOptions extends IPackageManagerOptions { + save?: boolean; + dev?: boolean; + optional?: boolean; +} + +interface IPackageManager { + add( + packageIdentifier: IPackageIdentifier, + options?: IPackageManagerAddOptions + ): Promise; + install( + packageIdentifier: IPackageIdentifier, + options?: IPackageManagerAddOptions + ): Promise; + + remove( + packageIdentifier: IPackageIdentifier, + options?: IPackageManagerRemoveOptions + ): Promise; + uninstall( + packageIdentifier: IPackageIdentifier, + options?: IPackageManagerRemoveOptions + ): Promise; + + getInstalledPackage( + packageIdentifier: IPackageIdentifier + ): Promise; + getPackageJson(packageIdentifier: IPackageIdentifier): Promise; +} diff --git a/lib/package-manager.ts b/lib/package-managers/index.ts similarity index 90% rename from lib/package-manager.ts rename to lib/package-managers/index.ts index 8bc54a92cf..cfe27e162f 100644 --- a/lib/package-manager.ts +++ b/lib/package-managers/index.ts @@ -1,6 +1,6 @@ -import { cache, exported, invokeInit } from "./common/decorators"; -import { performanceLog } from "./common/decorators"; -import { PackageManagers } from "./constants"; +import { cache, exported, invokeInit } from "../common/decorators"; +import { performanceLog } from "../common/decorators"; +import { PackageManagers } from "../constants"; import { IPackageManager, INodePackageManager, @@ -9,14 +9,14 @@ import { INpmInstallResultInfo, INpmsResult, INpmPackageNameParts, -} from "./declarations"; +} from "../declarations"; import { IErrors, IUserSettingsService, IDictionary, -} from "./common/declarations"; -import { injector } from "./common/yok"; -import { IProjectConfigService } from "./definitions/project"; +} from "../common/declarations"; +import { injector } from "../common/yok"; +import { IProjectConfigService } from "../definitions/project"; export class PackageManager implements IPackageManager { private packageManager: INodePackageManager; private _packageManagerName: string; @@ -174,4 +174,8 @@ export class PackageManager implements IPackageManager { } } +// export { NPM } from './npm' +// export { PNPM } from './pnpm' +// export { Yarn } from './yarn' + injector.register("packageManager", PackageManager); diff --git a/lib/node-package-manager.ts b/lib/package-managers/npm.ts similarity index 94% rename from lib/node-package-manager.ts rename to lib/package-managers/npm.ts index 254ff7e5e5..b8661dc258 100644 --- a/lib/node-package-manager.ts +++ b/lib/package-managers/npm.ts @@ -1,23 +1,23 @@ import { join, relative } from "path"; import { BasePackageManager } from "./base-package-manager"; -import { exported, cache } from "./common/decorators"; -import { CACACHE_DIRECTORY_NAME } from "./constants"; +import { exported, cache } from "../common/decorators"; +import { CACACHE_DIRECTORY_NAME } from "../constants"; import * as _ from "lodash"; import { INodePackageManagerInstallOptions, INpmInstallResultInfo, INpmsResult, -} from "./declarations"; +} from "../declarations"; import { IChildProcess, IErrors, IFileSystem, IHostInfo, Server, -} from "./common/declarations"; -import { injector } from "./common/yok"; +} from "../common/declarations"; +import { injector } from "../common/yok"; -export class NodePackageManager extends BasePackageManager { +export class NPM extends BasePackageManager { constructor( $childProcess: IChildProcess, private $errors: IErrors, @@ -172,4 +172,4 @@ export class NodePackageManager extends BasePackageManager { } } -injector.register("npm", NodePackageManager); +injector.register("npm", NPM); diff --git a/lib/package-installation-manager.ts b/lib/package-managers/package-installation-manager.ts similarity index 97% rename from lib/package-installation-manager.ts rename to lib/package-managers/package-installation-manager.ts index c8519bf02d..338000ec1f 100644 --- a/lib/package-installation-manager.ts +++ b/lib/package-managers/package-installation-manager.ts @@ -1,20 +1,20 @@ import * as path from "path"; -import * as constants from "./constants"; +import * as constants from "../constants"; import { INpmInstallOptions, INpmInstallResultInfo, IPackageInstallationManager, IPackageManager, IStaticConfig, -} from "./declarations"; -import { IProjectDataService } from "./definitions/project"; +} from "../declarations"; +import { IProjectDataService } from "../definitions/project"; import { IChildProcess, IDictionary, IFileSystem, ISettingsService, -} from "./common/declarations"; -import { injector } from "./common/yok"; +} from "../common/declarations"; +import { injector } from "../common/yok"; const semver = require("semver"); diff --git a/lib/pnpm-package-manager.ts b/lib/package-managers/pnpm.ts similarity index 92% rename from lib/pnpm-package-manager.ts rename to lib/package-managers/pnpm.ts index f2de683552..493bf86029 100644 --- a/lib/pnpm-package-manager.ts +++ b/lib/package-managers/pnpm.ts @@ -1,13 +1,13 @@ import * as path from "path"; import * as _ from "lodash"; import { BasePackageManager } from "./base-package-manager"; -import { exported } from "./common/decorators"; -import { CACACHE_DIRECTORY_NAME } from "./constants"; +import { exported } from "../common/decorators"; +import { CACACHE_DIRECTORY_NAME } from "../constants"; import { INodePackageManagerInstallOptions, INpmInstallResultInfo, INpmsResult, -} from "./declarations"; +} from "../declarations"; import { IChildProcess, IErrors, @@ -15,10 +15,10 @@ import { IHostInfo, Server, IDictionary, -} from "./common/declarations"; -import { injector } from "./common/yok"; +} from "../common/declarations"; +import { injector } from "../common/yok"; -export class PnpmPackageManager extends BasePackageManager { +export class PNPM extends BasePackageManager { constructor( $childProcess: IChildProcess, private $errors: IErrors, @@ -149,4 +149,4 @@ export class PnpmPackageManager extends BasePackageManager { } } -injector.register("pnpm", PnpmPackageManager); +injector.register("pnpm", PNPM); diff --git a/lib/yarn-package-manager.ts b/lib/package-managers/yarn.ts similarity index 93% rename from lib/yarn-package-manager.ts rename to lib/package-managers/yarn.ts index d4d08ad7f0..253f5efbd5 100644 --- a/lib/yarn-package-manager.ts +++ b/lib/package-managers/yarn.ts @@ -1,12 +1,12 @@ import * as path from "path"; import * as _ from "lodash"; import { BasePackageManager } from "./base-package-manager"; -import { exported } from "./common/decorators"; +import { exported } from "../common/decorators"; import { INodePackageManagerInstallOptions, INpmInstallResultInfo, INpmsResult, -} from "./declarations"; +} from "../declarations"; import { IChildProcess, IErrors, @@ -14,10 +14,10 @@ import { IHostInfo, Server, IDictionary, -} from "./common/declarations"; -import { injector } from "./common/yok"; +} from "../common/declarations"; +import { injector } from "../common/yok"; -export class YarnPackageManager extends BasePackageManager { +export class Yarn extends BasePackageManager { constructor( $childProcess: IChildProcess, private $errors: IErrors, @@ -147,4 +147,4 @@ export class YarnPackageManager extends BasePackageManager { } } -injector.register("yarn", YarnPackageManager); +injector.register("yarn", Yarn); diff --git a/lib/tools/node-modules/node-modules-builder.ts b/lib/tools/node-modules/node-modules-builder.ts index a7ba048de4..ac54f14acf 100644 --- a/lib/tools/node-modules/node-modules-builder.ts +++ b/lib/tools/node-modules/node-modules-builder.ts @@ -19,7 +19,8 @@ export class NodeModulesBuilder implements INodeModulesBuilder { projectData, }: IPrepareNodeModulesData): Promise { let dependencies = this.$nodeModulesDependenciesBuilder.getProductionDependencies( - projectData.projectDir, projectData.ignoredDependencies + projectData.projectDir, + projectData.ignoredDependencies ); dependencies = await platformData.platformProjectService.beforePrepareAllPlugins( projectData, diff --git a/packages/doctor/typings/interfaces.ts b/packages/doctor/typings/interfaces.ts index 867431c6d7..af9cccb685 100644 --- a/packages/doctor/typings/interfaces.ts +++ b/packages/doctor/typings/interfaces.ts @@ -209,7 +209,11 @@ declare module NativeScriptDoctor { * @param {string} runtimeVersion @optional The runtime version against which the validation is executed. In case this parameter is passed, it takes precedence over the projectDir argument. * @return {Promise} true if local build can be executed for the provided platform. */ - canExecuteLocalBuild(platform: string, projectDir?: string, runtimeVersion?: string): Promise; + canExecuteLocalBuild( + platform: string, + projectDir?: string, + runtimeVersion?: string + ): Promise; /** * Executes all checks for the current environment and returns the warnings from each check. @@ -363,7 +367,10 @@ declare module NativeScriptDoctor { dotNetVer?: string; } - interface ISysInfoData extends ICommonSysInfoData, IiOSSysInfoData, IAndroidSysInfoData { } + interface ISysInfoData + extends ICommonSysInfoData, + IiOSSysInfoData, + IAndroidSysInfoData {} /** * Describes warning returned from @nativescript/doctor check. @@ -487,7 +494,11 @@ declare module NativeScriptDoctor { * @param {string} runtimeVersion @optional The runtime version against which the validation is executed. In case this parameter is passed, it takes precedence over the projectDir argument. * @return {NativeScriptDoctor.IWarning[]} An array of errors from the validation checks. If there are no errors will return []. */ - validateJavacVersion(installedJavaVersion: string, projectDir?: string, runtimeVersion?: string): NativeScriptDoctor.IWarning[]; + validateJavacVersion( + installedJavaVersion: string, + projectDir?: string, + runtimeVersion?: string + ): NativeScriptDoctor.IWarning[]; /** * Returns the path to the adb which is located in ANDROID_HOME. @@ -506,14 +517,18 @@ declare module NativeScriptDoctor { * @param {ITargetValidationOptions} options The targetSdk to be validated and the project directory - used to determine the Android Runtime version. * @return {NativeScriptDoctor.IWarning[]} An array of errors from the validation checks. If there are no errors will return []. */ - validateMinSupportedTargetSdk(options: ITargetValidationOptions): NativeScriptDoctor.IWarning[]; + validateMinSupportedTargetSdk( + options: ITargetValidationOptions + ): NativeScriptDoctor.IWarning[]; /** * Validates if the provided targetSdk is lower that the maximum supported target SDK. * @param {ITargetValidationOptions} options The targetSdk to be validated and the project directory - used to determine the Android Runtime version. * @return {NativeScriptDoctor.IWarning[]} An array of errors from the validation checks. If there are no errors will return []. */ - validataMaxSupportedTargetSdk(options: ITargetValidationOptions): NativeScriptDoctor.IWarning[]; + validataMaxSupportedTargetSdk( + options: ITargetValidationOptions + ): NativeScriptDoctor.IWarning[]; /** * Returns the path to the emulator executable. diff --git a/packages/doctor/typings/nativescript-doctor.d.ts b/packages/doctor/typings/nativescript-doctor.d.ts index 3423e88e14..b0bbc8f08b 100644 --- a/packages/doctor/typings/nativescript-doctor.d.ts +++ b/packages/doctor/typings/nativescript-doctor.d.ts @@ -1,6 +1,5 @@ /// - declare module "@nativescript/doctor" { export const doctor: NativeScriptDoctor.IDoctor; export const sysInfo: NativeScriptDoctor.ISysInfo; diff --git a/test/controllers/add-platform-controller.ts b/test/controllers/add-platform-controller.ts index 8b98fe0ec3..b5d5d9cfe7 100644 --- a/test/controllers/add-platform-controller.ts +++ b/test/controllers/add-platform-controller.ts @@ -5,10 +5,10 @@ import { assert } from "chai"; import { format } from "util"; import * as _ from "lodash"; 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 { PnpmPackageManager } from "../../lib/pnpm-package-manager"; +import { PackageManager } from "../../lib/package-managers"; +import { NPM } from "../../lib/package-managers/npm"; +import { PNPM } from "../../lib/package-managers/pnpm"; +import { Yarn } from "../../lib/package-managers/yarn"; import { MobileHelper } from "../../lib/common/mobile/mobile-helper"; let actualMessage: string = null; @@ -27,9 +27,9 @@ function createInjector(data?: { latestFrameworkVersion: string }) { trackEventActionInGoogleAnalytics: () => ({}), }); injector.register("packageManager", PackageManager); - injector.register("npm", NodePackageManager); - injector.register("yarn", YarnPackageManager); - injector.register("pnpm", PnpmPackageManager); + injector.register("npm", NPM); + injector.register("yarn", Yarn); + injector.register("pnpm", PNPM); injector.register("userSettingsService", { getSettingValue: async (settingName: string): Promise => undefined, }); diff --git a/test/ios-project-service.ts b/test/ios-project-service.ts index 6de7894a86..55b0930041 100644 --- a/test/ios-project-service.ts +++ b/test/ios-project-service.ts @@ -24,9 +24,9 @@ import { IOSDeviceDiscovery } from "../lib/common/mobile/mobile-core/ios-device- import { AndroidDeviceDiscovery } from "../lib/common/mobile/mobile-core/android-device-discovery"; 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 { PackageManager } from "../lib/package-managers"; +import { NPM } from "../lib/package-managers/npm"; +import { Yarn } from "../lib/package-managers/yarn"; import { assert } from "chai"; import { SettingsService } from "../lib/common/test/unit-tests/stubs"; @@ -173,9 +173,9 @@ function createTestInjector( getSettingValue: async (settingName: string): Promise => undefined, }); testInjector.register("packageManager", PackageManager); + testInjector.register("npm", NPM); + testInjector.register("yarn", Yarn); testInjector.register("projectConfigService", ProjectConfigServiceStub); - testInjector.register("npm", NodePackageManager); - testInjector.register("yarn", YarnPackageManager); testInjector.register("xcconfigService", XcconfigService); testInjector.register("settingsService", SettingsService); testInjector.register("httpClient", {}); diff --git a/test/node-package-manager.ts b/test/node-package-manager.ts index 27efb270f3..79af0c71d0 100644 --- a/test/node-package-manager.ts +++ b/test/node-package-manager.ts @@ -1,7 +1,7 @@ import { Yok } from "../lib/common/yok"; import * as stubs from "./stubs"; import { assert } from "chai"; -import { NodePackageManager } from "../lib/node-package-manager"; +import { NPM } from "../lib/package-managers/npm"; import { IInjector } from "../lib/common/definitions/yok"; function createTestInjector(configuration: {} = {}): IInjector { @@ -12,7 +12,7 @@ function createTestInjector(configuration: {} = {}): IInjector { injector.register("childProcess", stubs.ChildProcessStub); injector.register("httpClient", {}); injector.register("fs", stubs.FileSystemStub); - injector.register("npm", NodePackageManager); + injector.register("npm", NPM); injector.register("pacoteService", { manifest: () => Promise.resolve(), }); @@ -52,7 +52,7 @@ describe("node-package-manager", () => { ].forEach((testCase) => { it(testCase.name, async () => { const testInjector = createTestInjector(); - const npm = testInjector.resolve("npm"); + const npm = testInjector.resolve("npm"); const templateNameParts = await npm.getPackageNameParts( testCase.templateFullName ); @@ -87,7 +87,7 @@ describe("node-package-manager", () => { ].forEach((testCase) => { it(testCase.name, async () => { const testInjector = createTestInjector(); - const npm = testInjector.resolve("npm"); + const npm = testInjector.resolve("npm"); const templateFullName = await npm.getPackageFullName({ name: testCase.templateName, version: testCase.templateVersion, diff --git a/test/package-installation-manager.ts b/test/package-installation-manager.ts index 9b9bcea58d..b1c425301a 100644 --- a/test/package-installation-manager.ts +++ b/test/package-installation-manager.ts @@ -4,11 +4,11 @@ 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/logger"; -import * as NpmLib from "../lib/node-package-manager"; -import * as YarnLib from "../lib/yarn-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"; +import * as NpmLib from "../lib/package-managers/npm"; +import * as YarnLib from "../lib/package-managers/yarn"; +import * as PnpmLib from "../lib/package-managers/pnpm"; +import * as PackageManagerLib from "../lib/package-managers"; +import * as PackageInstallationManagerLib from "../lib/package-managers/package-installation-manager"; import * as OptionsLib from "../lib/options"; import * as StaticConfigLib from "../lib/config"; import * as yok from "../lib/common/yok"; @@ -44,9 +44,9 @@ function createTestInjector(): IInjector { testInjector.register("userSettingsService", { getSettingValue: async (settingName: string): Promise => undefined, }); - testInjector.register("npm", NpmLib.NodePackageManager); - testInjector.register("yarn", YarnLib.YarnPackageManager); - testInjector.register("pnpm", PnpmLib.PnpmPackageManager); + testInjector.register("npm", NpmLib.NPM); + testInjector.register("pnpm", PnpmLib.PNPM); + testInjector.register("yarn", YarnLib.Yarn); testInjector.register("packageManager", PackageManagerLib.PackageManager); testInjector.register("projectConfigService", ProjectConfigServiceStub); testInjector.register( diff --git a/test/plugins-service.ts b/test/plugins-service.ts index d5b74fd4ba..a467d647f7 100644 --- a/test/plugins-service.ts +++ b/test/plugins-service.ts @@ -1,10 +1,10 @@ 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 { YarnPackageManager } from "../lib/yarn-package-manager"; -import { PnpmPackageManager } from "../lib/pnpm-package-manager"; +import { PackageManager } from "../lib/package-managers"; +import { PackageInstallationManager } from "../lib/package-managers/package-installation-manager"; +import { NPM } from "../lib/package-managers/npm"; +import { PNPM } from "../lib/package-managers/pnpm"; +import { Yarn } from "../lib/package-managers/yarn"; import { ProjectData } from "../lib/project-data"; import { ChildProcess } from "../lib/common/child-process"; import { Options } from "../lib/options"; @@ -68,13 +68,13 @@ function createTestInjector() { getSettingValue: async (settingName: string): Promise => undefined, }); testInjector.register("packageManager", PackageManager); + testInjector.register("npm", NPM); + testInjector.register("yarn", Yarn); + testInjector.register("pnpm", PNPM); testInjector.register( "projectConfigService", stubs.PackageInstallationManagerStub ); - testInjector.register("npm", NodePackageManager); - testInjector.register("yarn", YarnPackageManager); - testInjector.register("pnpm", PnpmPackageManager); 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 672ff84db0..d4b395e254 100644 --- a/test/services/extensibility-service.ts +++ b/test/services/extensibility-service.ts @@ -2,10 +2,10 @@ 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 { PnpmPackageManager } from "../../lib/pnpm-package-manager"; +import { PackageManager } from "../../lib/package-managers"; +import { NPM } from "../../lib/package-managers/npm"; +import { PNPM } from "../../lib/package-managers/pnpm"; +import { Yarn } from "../../lib/package-managers/yarn"; import * as constants from "../../lib/constants"; import { ChildProcess } from "../../lib/common/child-process"; import { CommandsDelimiters } from "../../lib/common/constants"; @@ -73,9 +73,9 @@ describe("extensibilityService", () => { testInjector.register("userSettingsService", { getSettingValue: async (settingName: string): Promise => undefined, }); - testInjector.register("npm", NodePackageManager); - testInjector.register("yarn", YarnPackageManager); - testInjector.register("pnpm", PnpmPackageManager); + testInjector.register("npm", NPM); + testInjector.register("yarn", Yarn); + testInjector.register("pnpm", PNPM); testInjector.register("settingsService", SettingsService); testInjector.register("requireService", { require: (pathToRequire: string): any => undefined, diff --git a/test/stubs.ts b/test/stubs.ts index 5e599f1671..1a8ddcc6d6 100644 --- a/test/stubs.ts +++ b/test/stubs.ts @@ -49,9 +49,7 @@ import { IDeviceDebugService, IDebugResultInfo, } from "../lib/definitions/debug"; -import { - IDependencyData, -} from "../lib/declarations"; +import { IDependencyData } from "../lib/declarations"; import { IBuildData } from "../lib/definitions/build"; import { IFileSystem, diff --git a/test/tools/node-modules/node-modules-dependencies-builder.ts b/test/tools/node-modules/node-modules-dependencies-builder.ts index 5494b159bb..7b5184e412 100644 --- a/test/tools/node-modules/node-modules-dependencies-builder.ts +++ b/test/tools/node-modules/node-modules-dependencies-builder.ts @@ -7,794 +7,794 @@ import * as constants from "../../../lib/constants"; import { IDependencyData } from "../../../lib/declarations"; import { INodeModulesDependenciesBuilder } from "../../../lib/definitions/platform"; import { IInjector } from "../../../lib/common/definitions/yok"; -import { IFileSystem, IStringDictionary, } from "../../../lib/common/declarations"; -import * as temp from 'temp' -import * as fs from 'fs'; +import { + IFileSystem, + IStringDictionary, +} from "../../../lib/common/declarations"; +import * as temp from "temp"; +import * as fs from "fs"; import { FileSystem } from "../../../lib/common/file-system"; interface IDependencyInfo { - name: string; - version: string; - depth: number; - dependencies?: IDependencyInfo[]; - nativescript?: any; - isDevDependency?: boolean; + name: string; + version: string; + depth: number; + dependencies?: IDependencyInfo[]; + nativescript?: any; + isDevDependency?: boolean; } // TODO: Add integration tests. // The tests assumes npm 3 or later is used, so all dependencies (and their dependencies) will be installed at the root node_modules describe("nodeModulesDependenciesBuilder", () => { - let pathToProject: string = 'test-project'; - - beforeEach(() => { - // we use realpath because os.tmpdir points to a symlink on macos - // and require.resolve resolves the symlink causing test failures - pathToProject = fs.realpathSync( - temp.mkdirSync("test-project") - ); - }) - - const getTestInjector = (): IInjector => { - const testInjector = new Yok(); - testInjector.register("fs", FileSystem); - - return testInjector; - }; - - describe("getProductionDependencies", () => { - describe("returns empty array", () => { - const validateResultIsEmpty = async (resultOfReadJson: any) => { - const testInjector = getTestInjector(); - const fs = testInjector.resolve("fs"); - fs.readJson = (filename: string, encoding?: string): any => { - return resultOfReadJson; - }; - - const nodeModulesDependenciesBuilder = testInjector.resolve(NodeModulesDependenciesBuilder); - const result = await nodeModulesDependenciesBuilder.getProductionDependencies( - pathToProject - ); - - assert.deepStrictEqual(result, []); - }; - - it("when package.json does not have any data", async () => { - await validateResultIsEmpty(null); - }); - - it("when package.json does not have dependencies section", async () => { - await validateResultIsEmpty({ - name: "some name", - devDependencies: { a: "1.0.0" }, - }); - }); - }); - - describe("returns correct dependencies", () => { - /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * - * Helper functions for easier writing of consecutive tests in the suite. * - * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ - - const getPathToDependencyInNodeModules = ( - dependencyName: string, - parentDir?: string - ): string => { - return path.join( - parentDir ?? pathToProject, - constants.NODE_MODULES_FOLDER_NAME, - dependencyName - ); - }; - - const getNodeModuleInfoForExpectedDependency = ( - dir: string, - depth: number, - nativescript?: any, - dependencies?: string[], - name?: string, - version?: string - ): IDependencyData => { - const packageName = name ?? path.basename(dir); - const result: IDependencyData = { - name: packageName, - directory: getPathToDependencyInNodeModules(dir), - depth, - dependencies: dependencies || [], - version: version, - }; - - if (nativescript) { - result.nativescript = nativescript; - } - - return result; - }; - - const getPathToPackageJsonOfDependency = ( - dependencyName: string, - parentDir?: string - ): string => { - return path.join( - getPathToDependencyInNodeModules(dependencyName, parentDir), - constants.PACKAGE_JSON_FILE_NAME - ); - }; - - const getDependenciesObjectFromDependencyInfo = ( - depInfos: IDependencyInfo[], - nativescript: any, - version: string - ): { - dependencies: IStringDictionary; - nativescript?: any; - devDependencies: IStringDictionary; - version: string; - } => { - const dependencies: any = {}; - const devDependencies: any = {}; - _.each(depInfos, (innerDependency) => { - if (innerDependency.isDevDependency) { - devDependencies[innerDependency.name] = innerDependency.version; - } else { - dependencies[innerDependency.name] = innerDependency.version; - } - }); - - const result: any = { - dependencies, - devDependencies, - }; - - if (nativescript) { - result.nativescript = nativescript; - } - - if (version) { - result.version = version; - } - - return result; - }; - - const getDependenciesObject = ( - filename: string, - deps: IDependencyInfo[], - parentDir: string - ): { - dependencies: IStringDictionary; - nativescript?: any; - devDependencies: IStringDictionary; - } => { - let result: { - dependencies: IStringDictionary; - nativescript?: any; - devDependencies: IStringDictionary; - } = null; - for (const dependencyInfo of deps) { - const pathToPackageJson = getPathToPackageJsonOfDependency( - dependencyInfo.name, - parentDir - ); - if (filename === pathToPackageJson) { - return getDependenciesObjectFromDependencyInfo( - dependencyInfo.dependencies, - dependencyInfo.nativescript, - dependencyInfo.version - ); - } - - if (dependencyInfo.dependencies) { - result = getDependenciesObject( - filename, - dependencyInfo.dependencies, - path.join( - parentDir, - constants.NODE_MODULES_FOLDER_NAME, - dependencyInfo.name - ) - ); - if (result) { - break; - } - } - } - - return result; - }; - - const generatePackageJsonData = ( - dep: IDependencyInfo - ) => { - const data: any = { - name: dep.name, - version: dep.version, - dependencies: dep.dependencies?.reduce((deps, dep) => { - if (!dep.isDevDependency) { - deps[dep.name] = dep.version - } - return deps; - }, {} as { [name: string]: string }), - devDependencies: dep.dependencies?.reduce((deps, dep) => { - if (dep.isDevDependency) { - deps[dep.name] = dep.version - } - return deps; - }, {} as { [name: string]: string }) - } - - if(dep.nativescript) { - data.nativescript = dep.nativescript; - } - - return data; - } - - const generateNodeModules = ( - dep: IDependencyInfo, - rootPath: string) => { - // ensure dep directory - fs.mkdirSync(rootPath, { recursive: true }); - - // generate package.json contents - const packageJsonData = generatePackageJsonData(dep); - - // write package.json - fs.writeFileSync( - path.join(rootPath, 'package.json'), - JSON.stringify(packageJsonData) - ) - - // recurse into sub-dependencies if any - if (dep.dependencies) { - for (const subDep of dep.dependencies) { - generateNodeModules( - subDep, - path.join(rootPath, 'node_modules', subDep.name) - ); - } - } - } - - const generateTest = ( - rootDeps: IDependencyInfo[] - ): INodeModulesDependenciesBuilder => { - const testInjector = getTestInjector(); - const nodeModulesDependenciesBuilder = testInjector.resolve(NodeModulesDependenciesBuilder); - - generateNodeModules( - { - name: 'test-project', - version: '1.0.0', - depth: 0, - dependencies: rootDeps - }, - pathToProject - ); - - return nodeModulesDependenciesBuilder; - }; - - const generateDependency = ( - name: string, - version: string, - depth: number, - dependencies: IDependencyInfo[], - nativescript?: any, - opts?: { isDevDependency: boolean } - ): IDependencyInfo => { - return { - name, - version, - depth, - dependencies, - nativescript, - isDevDependency: !!(opts && opts.isDevDependency), - }; - }; - - /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * - * END of helper functions for easier writing of consecutive tests in the suite. * - * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ - - const firstPackage = "firstPackage"; - const secondPackage = "secondPackage"; - const thirdPackage = "thirdPackage"; - - it("when there are both dependencies and devDependencies installed", async () => { - // The test validates the following dependency tree, when npm 3+ is used. - // - // ├── firstPackage@1.0.0 - // ├── secondPackage@1.1.0 - // └── thirdPackage@1.2.0 // this is devDependency - - const rootDeps: IDependencyInfo[] = [ - generateDependency(firstPackage, "1.0.0", 0, null), - generateDependency(secondPackage, "1.1.0", 0, null), - generateDependency(thirdPackage, "1.2.0", 0, null, null, { - isDevDependency: true, - }), - ]; - - const nodeModulesDependenciesBuilder = generateTest(rootDeps); - const actualResult = await nodeModulesDependenciesBuilder.getProductionDependencies( - pathToProject - ); - - const expectedResult: IDependencyData[] = [ - getNodeModuleInfoForExpectedDependency( - firstPackage, - 0, - null, - null, - null, - "1.0.0" - ), - getNodeModuleInfoForExpectedDependency( - secondPackage, - 0, - null, - null, - null, - "1.1.0" - ), - ]; - - assert.deepStrictEqual(actualResult, expectedResult); - }); - - it("when there are both dependencies and devDependencies installed, does not handle dependencies of devDependencies", async () => { - // The test validates the following dependency tree, when npm 3+ is used. - // - // ├─┬ firstPackage@1.0.0 // this is devDependency - // │ └── secondPackage@1.2.0 - // └── secondPackage@1.1.0 - - const rootDeps: IDependencyInfo[] = [ - generateDependency( - firstPackage, - "1.0.0", - 0, - [generateDependency(secondPackage, "1.2.0", 1, null)], - null, - { isDevDependency: true } - ), - generateDependency(secondPackage, "1.1.0", 0, null), - ]; - - const expectedResult: IDependencyData[] = [ - getNodeModuleInfoForExpectedDependency( - secondPackage, - 0, - null, - null, - null, - "1.1.0" - ), - ]; - - const nodeModulesDependenciesBuilder = generateTest(rootDeps); - const actualResult = await nodeModulesDependenciesBuilder.getProductionDependencies( - pathToProject - ); - assert.deepStrictEqual(actualResult, expectedResult); - }); - - it("when there are scoped dependencies", async () => { - // The test validates the following dependency tree, when npm 3+ is used. - // - // ├─┬ @scope/firstPackage@1.0.0 - // │ └── secondPackage@1.2.0 - // └── secondPackage@1.1.0 - - const scopedPackageName = `@scope/${firstPackage}`; - const rootDeps: IDependencyInfo[] = [ - generateDependency(scopedPackageName, "1.0.0", 0, [ - generateDependency(secondPackage, "1.2.0", 1, null), - ]), - generateDependency(secondPackage, "1.1.0", 0, null), - ]; - - const expectedResult: IDependencyData[] = [ - getNodeModuleInfoForExpectedDependency( - scopedPackageName, - 0, - null, - [secondPackage], - scopedPackageName, - "1.0.0" - ), - getNodeModuleInfoForExpectedDependency( - secondPackage, - 0, - null, - null, - null, - "1.1.0" - ), - getNodeModuleInfoForExpectedDependency( - path.join( - scopedPackageName, - constants.NODE_MODULES_FOLDER_NAME, - secondPackage - ), - 1, - null, - null, - null, - "1.2.0" - ), - ]; - - const nodeModulesDependenciesBuilder = generateTest(rootDeps); - const actualResult = await nodeModulesDependenciesBuilder.getProductionDependencies( - pathToProject - ); - assert.deepStrictEqual(actualResult, expectedResult); - }); - - it("when there are scoped dependencies as dependency of other non-scoped dependency", async () => { - // The test validates the following dependency tree, when npm 3+ is used. - // - // ├─┬ firstPackage@1.0.0 - // │ └── @scope/secondPackage@1.2.0 - // └── thirdPackage@1.1.0 - - const scopedPackageName = `@scope/${secondPackage}`; - const rootDeps: IDependencyInfo[] = [ - generateDependency(firstPackage, "1.0.0", 0, [ - generateDependency(scopedPackageName, "1.2.0", 1, null), - ]), - generateDependency(thirdPackage, "1.1.0", 0, null), - ]; - - const expectedResult: IDependencyData[] = [ - getNodeModuleInfoForExpectedDependency( - firstPackage, - 0, - null, - [scopedPackageName], - null, - "1.0.0" - ), - getNodeModuleInfoForExpectedDependency( - thirdPackage, - 0, - null, - null, - null, - "1.1.0" - ), - getNodeModuleInfoForExpectedDependency( - path.join( - firstPackage, - constants.NODE_MODULES_FOLDER_NAME, - scopedPackageName - ), - 1, - null, - null, - scopedPackageName, - "1.2.0" - ), - ]; - - const nodeModulesDependenciesBuilder = generateTest(rootDeps); - const actualResult = await nodeModulesDependenciesBuilder.getProductionDependencies( - pathToProject - ); - assert.deepStrictEqual(actualResult, expectedResult); - }); - - it("when all dependencies are installed at the root level of the project", async () => { - // The test validates the following dependency tree, when npm 3+ is used. - // - // ├── firstPackage@1.0.0 - // ├── secondPackage@1.1.0 - // └── thirdPackage@1.2.0 - - const rootDeps: IDependencyInfo[] = [ - generateDependency(firstPackage, "1.0.0", 0, null), - generateDependency(secondPackage, "1.1.0", 0, null), - generateDependency(thirdPackage, "1.2.0", 0, null), - ]; - - const nodeModulesDependenciesBuilder = generateTest(rootDeps); - const actualResult = await nodeModulesDependenciesBuilder.getProductionDependencies( - pathToProject - ); - - const expectedResult: IDependencyData[] = [ - getNodeModuleInfoForExpectedDependency( - firstPackage, - 0, - null, - null, - null, - "1.0.0" - ), - getNodeModuleInfoForExpectedDependency( - secondPackage, - 0, - null, - null, - null, - "1.1.0" - ), - getNodeModuleInfoForExpectedDependency( - thirdPackage, - 0, - null, - null, - null, - "1.2.0" - ), - ]; - - assert.deepStrictEqual(actualResult, expectedResult); - }); - - it("when the project has a dependency to a package and one of the other packages has dependency to other version of this package", async () => { - // The test validates the following dependency tree, when npm 3+ is used. - // - // ├─┬ firstPackage@1.0.0 - // │ └── secondPackage@1.2.0 - // └── secondPackage@1.1.0 - - const rootDeps: IDependencyInfo[] = [ - generateDependency(firstPackage, "1.0.0", 0, [ - generateDependency(secondPackage, "1.2.0", 1, null), - ]), - generateDependency(secondPackage, "1.1.0", 0, null), - ]; - - const expectedResult: IDependencyData[] = [ - getNodeModuleInfoForExpectedDependency( - firstPackage, - 0, - null, - [secondPackage], - null, - "1.0.0" - ), - getNodeModuleInfoForExpectedDependency( - secondPackage, - 0, - null, - null, - null, - "1.1.0" - ), - getNodeModuleInfoForExpectedDependency( - path.join( - firstPackage, - constants.NODE_MODULES_FOLDER_NAME, - secondPackage - ), - 1, - null, - null, - null, - "1.2.0" - ), - ]; - - const nodeModulesDependenciesBuilder = generateTest(rootDeps); - const actualResult = await nodeModulesDependenciesBuilder.getProductionDependencies( - pathToProject - ); - assert.deepStrictEqual(actualResult, expectedResult); - }); - - it("when several package depend on different versions of other packages", async () => { - // The test validates the following dependency tree, when npm 3+ is used. - // - // ├─┬ firstPackage@1.0.0 - // │ ├─┬ secondPackage@1.1.0 - // │ │ └── thirdPackage@1.2.0 - // │ └── thirdPackage@1.1.0 - // ├── secondPackage@1.0.0 - // └── thirdPackage@1.0.0 - - const rootDeps: IDependencyInfo[] = [ - generateDependency(firstPackage, "1.0.0", 0, [ - generateDependency(secondPackage, "1.1.0", 1, [ - generateDependency(thirdPackage, "1.2.0", 2, null), - ]), - generateDependency(thirdPackage, "1.1.0", 1, null), - ]), - generateDependency(secondPackage, "1.0.0", 0, null), - generateDependency(thirdPackage, "1.0.0", 0, null), - ]; - - const pathToSecondPackageInsideFirstPackage = path.join( - firstPackage, - constants.NODE_MODULES_FOLDER_NAME, - secondPackage - ); - const expectedResult: IDependencyData[] = [ - getNodeModuleInfoForExpectedDependency( - firstPackage, - 0, - null, - [secondPackage, thirdPackage], - null, - "1.0.0" - ), - getNodeModuleInfoForExpectedDependency( - secondPackage, - 0, - null, - null, - null, - "1.0.0" - ), - getNodeModuleInfoForExpectedDependency( - thirdPackage, - 0, - null, - null, - null, - "1.0.0" - ), - getNodeModuleInfoForExpectedDependency( - pathToSecondPackageInsideFirstPackage, - 1, - null, - [thirdPackage], - null, - "1.1.0" - ), - getNodeModuleInfoForExpectedDependency( - path.join( - firstPackage, - constants.NODE_MODULES_FOLDER_NAME, - thirdPackage - ), - 1, - null, - null, - null, - "1.1.0" - ), - getNodeModuleInfoForExpectedDependency( - path.join( - pathToSecondPackageInsideFirstPackage, - constants.NODE_MODULES_FOLDER_NAME, - thirdPackage - ), - 2, - null, - null, - null, - "1.2.0" - ), - ]; - - const nodeModulesDependenciesBuilder = generateTest(rootDeps); - const actualResult = await nodeModulesDependenciesBuilder.getProductionDependencies( - pathToProject - ); - assert.deepStrictEqual(actualResult, expectedResult); - }); - - it("when the installed packages have nativescript data in their package.json", async () => { - // The test validates the following dependency tree, when npm 3+ is used. - // - // ├── firstPackage@1.0.0 - // ├── secondPackage@1.1.0 - // └── thirdPackage@1.2.0 - - const getNativeScriptDataForPlugin = (pluginName: string): any => { - return { - platforms: { - "tns-android": "x.x.x", - "tns-ios": "x.x.x", - }, - - customPropertyUsedForThisTestOnly: pluginName, - }; - }; - - const rootDeps: IDependencyInfo[] = [ - generateDependency( - firstPackage, - "1.0.0", - 0, - null, - getNativeScriptDataForPlugin(firstPackage) - ), - generateDependency( - secondPackage, - "1.1.0", - 0, - null, - getNativeScriptDataForPlugin(secondPackage) - ), - generateDependency( - thirdPackage, - "1.2.0", - 0, - null, - getNativeScriptDataForPlugin(thirdPackage) - ), - ]; - - const nodeModulesDependenciesBuilder = generateTest(rootDeps); - const actualResult = await nodeModulesDependenciesBuilder.getProductionDependencies( - pathToProject - ); - - const expectedResult: IDependencyData[] = [ - getNodeModuleInfoForExpectedDependency( - firstPackage, - 0, - getNativeScriptDataForPlugin(firstPackage), - null, - null, - "1.0.0" - ), - getNodeModuleInfoForExpectedDependency( - secondPackage, - 0, - getNativeScriptDataForPlugin(secondPackage), - null, - null, - "1.1.0" - ), - getNodeModuleInfoForExpectedDependency( - thirdPackage, - 0, - getNativeScriptDataForPlugin(thirdPackage), - null, - null, - "1.2.0" - ), - ]; - - assert.deepStrictEqual(actualResult, expectedResult); - }); - - it("ignoring dependencies", async () => { - // The test validates the following dependency tree, when npm 3+ is used. - // - // ├── firstPackage@1.0.0 - // ├── secondPackage@1.1.0 - // └── thirdPackage@1.2.0 - - const rootDeps: IDependencyInfo[] = [ - generateDependency(firstPackage, "1.0.0", 0, null), - generateDependency(secondPackage, "1.1.0", 0, null), - generateDependency(thirdPackage, "1.2.0", 0, null), - ]; - - const nodeModulesDependenciesBuilder = generateTest(rootDeps); - const actualResult = await nodeModulesDependenciesBuilder.getProductionDependencies( - pathToProject, [firstPackage] - ); - - const expectedResult: IDependencyData[] = [ - getNodeModuleInfoForExpectedDependency( - secondPackage, - 0, - null, - null, - null, - "1.1.0" - ), - getNodeModuleInfoForExpectedDependency( - thirdPackage, - 0, - null, - null, - null, - "1.2.0" - ), - ]; - - assert.deepStrictEqual(actualResult, expectedResult); - }); - }); - }) - ; -}) -; + let pathToProject: string = "test-project"; + + beforeEach(() => { + // we use realpath because os.tmpdir points to a symlink on macos + // and require.resolve resolves the symlink causing test failures + pathToProject = fs.realpathSync(temp.mkdirSync("test-project")); + }); + + const getTestInjector = (): IInjector => { + const testInjector = new Yok(); + testInjector.register("fs", FileSystem); + + return testInjector; + }; + + describe("getProductionDependencies", () => { + describe("returns empty array", () => { + const validateResultIsEmpty = async (resultOfReadJson: any) => { + const testInjector = getTestInjector(); + const fs = testInjector.resolve("fs"); + fs.readJson = (filename: string, encoding?: string): any => { + return resultOfReadJson; + }; + + const nodeModulesDependenciesBuilder = testInjector.resolve< + INodeModulesDependenciesBuilder + >(NodeModulesDependenciesBuilder); + const result = await nodeModulesDependenciesBuilder.getProductionDependencies( + pathToProject + ); + + assert.deepStrictEqual(result, []); + }; + + it("when package.json does not have any data", async () => { + await validateResultIsEmpty(null); + }); + + it("when package.json does not have dependencies section", async () => { + await validateResultIsEmpty({ + name: "some name", + devDependencies: { a: "1.0.0" }, + }); + }); + }); + + describe("returns correct dependencies", () => { + /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Helper functions for easier writing of consecutive tests in the suite. * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + + const getPathToDependencyInNodeModules = ( + dependencyName: string, + parentDir?: string + ): string => { + return path.join( + parentDir ?? pathToProject, + constants.NODE_MODULES_FOLDER_NAME, + dependencyName + ); + }; + + const getNodeModuleInfoForExpectedDependency = ( + dir: string, + depth: number, + nativescript?: any, + dependencies?: string[], + name?: string, + version?: string + ): IDependencyData => { + const packageName = name ?? path.basename(dir); + const result: IDependencyData = { + name: packageName, + directory: getPathToDependencyInNodeModules(dir), + depth, + dependencies: dependencies || [], + version: version, + }; + + if (nativescript) { + result.nativescript = nativescript; + } + + return result; + }; + + const getPathToPackageJsonOfDependency = ( + dependencyName: string, + parentDir?: string + ): string => { + return path.join( + getPathToDependencyInNodeModules(dependencyName, parentDir), + constants.PACKAGE_JSON_FILE_NAME + ); + }; + + const getDependenciesObjectFromDependencyInfo = ( + depInfos: IDependencyInfo[], + nativescript: any, + version: string + ): { + dependencies: IStringDictionary; + nativescript?: any; + devDependencies: IStringDictionary; + version: string; + } => { + const dependencies: any = {}; + const devDependencies: any = {}; + _.each(depInfos, (innerDependency) => { + if (innerDependency.isDevDependency) { + devDependencies[innerDependency.name] = innerDependency.version; + } else { + dependencies[innerDependency.name] = innerDependency.version; + } + }); + + const result: any = { + dependencies, + devDependencies, + }; + + if (nativescript) { + result.nativescript = nativescript; + } + + if (version) { + result.version = version; + } + + return result; + }; + + const getDependenciesObject = ( + filename: string, + deps: IDependencyInfo[], + parentDir: string + ): { + dependencies: IStringDictionary; + nativescript?: any; + devDependencies: IStringDictionary; + } => { + let result: { + dependencies: IStringDictionary; + nativescript?: any; + devDependencies: IStringDictionary; + } = null; + for (const dependencyInfo of deps) { + const pathToPackageJson = getPathToPackageJsonOfDependency( + dependencyInfo.name, + parentDir + ); + if (filename === pathToPackageJson) { + return getDependenciesObjectFromDependencyInfo( + dependencyInfo.dependencies, + dependencyInfo.nativescript, + dependencyInfo.version + ); + } + + if (dependencyInfo.dependencies) { + result = getDependenciesObject( + filename, + dependencyInfo.dependencies, + path.join( + parentDir, + constants.NODE_MODULES_FOLDER_NAME, + dependencyInfo.name + ) + ); + if (result) { + break; + } + } + } + + return result; + }; + + const generatePackageJsonData = (dep: IDependencyInfo) => { + const data: any = { + name: dep.name, + version: dep.version, + dependencies: dep.dependencies?.reduce((deps, dep) => { + if (!dep.isDevDependency) { + deps[dep.name] = dep.version; + } + return deps; + }, {} as { [name: string]: string }), + devDependencies: dep.dependencies?.reduce((deps, dep) => { + if (dep.isDevDependency) { + deps[dep.name] = dep.version; + } + return deps; + }, {} as { [name: string]: string }), + }; + + if (dep.nativescript) { + data.nativescript = dep.nativescript; + } + + return data; + }; + + const generateNodeModules = (dep: IDependencyInfo, rootPath: string) => { + // ensure dep directory + fs.mkdirSync(rootPath, { recursive: true }); + + // generate package.json contents + const packageJsonData = generatePackageJsonData(dep); + + // write package.json + fs.writeFileSync( + path.join(rootPath, "package.json"), + JSON.stringify(packageJsonData) + ); + + // recurse into sub-dependencies if any + if (dep.dependencies) { + for (const subDep of dep.dependencies) { + generateNodeModules( + subDep, + path.join(rootPath, "node_modules", subDep.name) + ); + } + } + }; + + const generateTest = ( + rootDeps: IDependencyInfo[] + ): INodeModulesDependenciesBuilder => { + const testInjector = getTestInjector(); + const nodeModulesDependenciesBuilder = testInjector.resolve< + INodeModulesDependenciesBuilder + >(NodeModulesDependenciesBuilder); + + generateNodeModules( + { + name: "test-project", + version: "1.0.0", + depth: 0, + dependencies: rootDeps, + }, + pathToProject + ); + + return nodeModulesDependenciesBuilder; + }; + + const generateDependency = ( + name: string, + version: string, + depth: number, + dependencies: IDependencyInfo[], + nativescript?: any, + opts?: { isDevDependency: boolean } + ): IDependencyInfo => { + return { + name, + version, + depth, + dependencies, + nativescript, + isDevDependency: !!(opts && opts.isDevDependency), + }; + }; + + /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * END of helper functions for easier writing of consecutive tests in the suite. * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + + const firstPackage = "firstPackage"; + const secondPackage = "secondPackage"; + const thirdPackage = "thirdPackage"; + + it("when there are both dependencies and devDependencies installed", async () => { + // The test validates the following dependency tree, when npm 3+ is used. + // + // ├── firstPackage@1.0.0 + // ├── secondPackage@1.1.0 + // └── thirdPackage@1.2.0 // this is devDependency + + const rootDeps: IDependencyInfo[] = [ + generateDependency(firstPackage, "1.0.0", 0, null), + generateDependency(secondPackage, "1.1.0", 0, null), + generateDependency(thirdPackage, "1.2.0", 0, null, null, { + isDevDependency: true, + }), + ]; + + const nodeModulesDependenciesBuilder = generateTest(rootDeps); + const actualResult = await nodeModulesDependenciesBuilder.getProductionDependencies( + pathToProject + ); + + const expectedResult: IDependencyData[] = [ + getNodeModuleInfoForExpectedDependency( + firstPackage, + 0, + null, + null, + null, + "1.0.0" + ), + getNodeModuleInfoForExpectedDependency( + secondPackage, + 0, + null, + null, + null, + "1.1.0" + ), + ]; + + assert.deepStrictEqual(actualResult, expectedResult); + }); + + it("when there are both dependencies and devDependencies installed, does not handle dependencies of devDependencies", async () => { + // The test validates the following dependency tree, when npm 3+ is used. + // + // ├─┬ firstPackage@1.0.0 // this is devDependency + // │ └── secondPackage@1.2.0 + // └── secondPackage@1.1.0 + + const rootDeps: IDependencyInfo[] = [ + generateDependency( + firstPackage, + "1.0.0", + 0, + [generateDependency(secondPackage, "1.2.0", 1, null)], + null, + { isDevDependency: true } + ), + generateDependency(secondPackage, "1.1.0", 0, null), + ]; + + const expectedResult: IDependencyData[] = [ + getNodeModuleInfoForExpectedDependency( + secondPackage, + 0, + null, + null, + null, + "1.1.0" + ), + ]; + + const nodeModulesDependenciesBuilder = generateTest(rootDeps); + const actualResult = await nodeModulesDependenciesBuilder.getProductionDependencies( + pathToProject + ); + assert.deepStrictEqual(actualResult, expectedResult); + }); + + it("when there are scoped dependencies", async () => { + // The test validates the following dependency tree, when npm 3+ is used. + // + // ├─┬ @scope/firstPackage@1.0.0 + // │ └── secondPackage@1.2.0 + // └── secondPackage@1.1.0 + + const scopedPackageName = `@scope/${firstPackage}`; + const rootDeps: IDependencyInfo[] = [ + generateDependency(scopedPackageName, "1.0.0", 0, [ + generateDependency(secondPackage, "1.2.0", 1, null), + ]), + generateDependency(secondPackage, "1.1.0", 0, null), + ]; + + const expectedResult: IDependencyData[] = [ + getNodeModuleInfoForExpectedDependency( + scopedPackageName, + 0, + null, + [secondPackage], + scopedPackageName, + "1.0.0" + ), + getNodeModuleInfoForExpectedDependency( + secondPackage, + 0, + null, + null, + null, + "1.1.0" + ), + getNodeModuleInfoForExpectedDependency( + path.join( + scopedPackageName, + constants.NODE_MODULES_FOLDER_NAME, + secondPackage + ), + 1, + null, + null, + null, + "1.2.0" + ), + ]; + + const nodeModulesDependenciesBuilder = generateTest(rootDeps); + const actualResult = await nodeModulesDependenciesBuilder.getProductionDependencies( + pathToProject + ); + assert.deepStrictEqual(actualResult, expectedResult); + }); + + it("when there are scoped dependencies as dependency of other non-scoped dependency", async () => { + // The test validates the following dependency tree, when npm 3+ is used. + // + // ├─┬ firstPackage@1.0.0 + // │ └── @scope/secondPackage@1.2.0 + // └── thirdPackage@1.1.0 + + const scopedPackageName = `@scope/${secondPackage}`; + const rootDeps: IDependencyInfo[] = [ + generateDependency(firstPackage, "1.0.0", 0, [ + generateDependency(scopedPackageName, "1.2.0", 1, null), + ]), + generateDependency(thirdPackage, "1.1.0", 0, null), + ]; + + const expectedResult: IDependencyData[] = [ + getNodeModuleInfoForExpectedDependency( + firstPackage, + 0, + null, + [scopedPackageName], + null, + "1.0.0" + ), + getNodeModuleInfoForExpectedDependency( + thirdPackage, + 0, + null, + null, + null, + "1.1.0" + ), + getNodeModuleInfoForExpectedDependency( + path.join( + firstPackage, + constants.NODE_MODULES_FOLDER_NAME, + scopedPackageName + ), + 1, + null, + null, + scopedPackageName, + "1.2.0" + ), + ]; + + const nodeModulesDependenciesBuilder = generateTest(rootDeps); + const actualResult = await nodeModulesDependenciesBuilder.getProductionDependencies( + pathToProject + ); + assert.deepStrictEqual(actualResult, expectedResult); + }); + + it("when all dependencies are installed at the root level of the project", async () => { + // The test validates the following dependency tree, when npm 3+ is used. + // + // ├── firstPackage@1.0.0 + // ├── secondPackage@1.1.0 + // └── thirdPackage@1.2.0 + + const rootDeps: IDependencyInfo[] = [ + generateDependency(firstPackage, "1.0.0", 0, null), + generateDependency(secondPackage, "1.1.0", 0, null), + generateDependency(thirdPackage, "1.2.0", 0, null), + ]; + + const nodeModulesDependenciesBuilder = generateTest(rootDeps); + const actualResult = await nodeModulesDependenciesBuilder.getProductionDependencies( + pathToProject + ); + + const expectedResult: IDependencyData[] = [ + getNodeModuleInfoForExpectedDependency( + firstPackage, + 0, + null, + null, + null, + "1.0.0" + ), + getNodeModuleInfoForExpectedDependency( + secondPackage, + 0, + null, + null, + null, + "1.1.0" + ), + getNodeModuleInfoForExpectedDependency( + thirdPackage, + 0, + null, + null, + null, + "1.2.0" + ), + ]; + + assert.deepStrictEqual(actualResult, expectedResult); + }); + + it("when the project has a dependency to a package and one of the other packages has dependency to other version of this package", async () => { + // The test validates the following dependency tree, when npm 3+ is used. + // + // ├─┬ firstPackage@1.0.0 + // │ └── secondPackage@1.2.0 + // └── secondPackage@1.1.0 + + const rootDeps: IDependencyInfo[] = [ + generateDependency(firstPackage, "1.0.0", 0, [ + generateDependency(secondPackage, "1.2.0", 1, null), + ]), + generateDependency(secondPackage, "1.1.0", 0, null), + ]; + + const expectedResult: IDependencyData[] = [ + getNodeModuleInfoForExpectedDependency( + firstPackage, + 0, + null, + [secondPackage], + null, + "1.0.0" + ), + getNodeModuleInfoForExpectedDependency( + secondPackage, + 0, + null, + null, + null, + "1.1.0" + ), + getNodeModuleInfoForExpectedDependency( + path.join( + firstPackage, + constants.NODE_MODULES_FOLDER_NAME, + secondPackage + ), + 1, + null, + null, + null, + "1.2.0" + ), + ]; + + const nodeModulesDependenciesBuilder = generateTest(rootDeps); + const actualResult = await nodeModulesDependenciesBuilder.getProductionDependencies( + pathToProject + ); + assert.deepStrictEqual(actualResult, expectedResult); + }); + + it("when several package depend on different versions of other packages", async () => { + // The test validates the following dependency tree, when npm 3+ is used. + // + // ├─┬ firstPackage@1.0.0 + // │ ├─┬ secondPackage@1.1.0 + // │ │ └── thirdPackage@1.2.0 + // │ └── thirdPackage@1.1.0 + // ├── secondPackage@1.0.0 + // └── thirdPackage@1.0.0 + + const rootDeps: IDependencyInfo[] = [ + generateDependency(firstPackage, "1.0.0", 0, [ + generateDependency(secondPackage, "1.1.0", 1, [ + generateDependency(thirdPackage, "1.2.0", 2, null), + ]), + generateDependency(thirdPackage, "1.1.0", 1, null), + ]), + generateDependency(secondPackage, "1.0.0", 0, null), + generateDependency(thirdPackage, "1.0.0", 0, null), + ]; + + const pathToSecondPackageInsideFirstPackage = path.join( + firstPackage, + constants.NODE_MODULES_FOLDER_NAME, + secondPackage + ); + const expectedResult: IDependencyData[] = [ + getNodeModuleInfoForExpectedDependency( + firstPackage, + 0, + null, + [secondPackage, thirdPackage], + null, + "1.0.0" + ), + getNodeModuleInfoForExpectedDependency( + secondPackage, + 0, + null, + null, + null, + "1.0.0" + ), + getNodeModuleInfoForExpectedDependency( + thirdPackage, + 0, + null, + null, + null, + "1.0.0" + ), + getNodeModuleInfoForExpectedDependency( + pathToSecondPackageInsideFirstPackage, + 1, + null, + [thirdPackage], + null, + "1.1.0" + ), + getNodeModuleInfoForExpectedDependency( + path.join( + firstPackage, + constants.NODE_MODULES_FOLDER_NAME, + thirdPackage + ), + 1, + null, + null, + null, + "1.1.0" + ), + getNodeModuleInfoForExpectedDependency( + path.join( + pathToSecondPackageInsideFirstPackage, + constants.NODE_MODULES_FOLDER_NAME, + thirdPackage + ), + 2, + null, + null, + null, + "1.2.0" + ), + ]; + + const nodeModulesDependenciesBuilder = generateTest(rootDeps); + const actualResult = await nodeModulesDependenciesBuilder.getProductionDependencies( + pathToProject + ); + assert.deepStrictEqual(actualResult, expectedResult); + }); + + it("when the installed packages have nativescript data in their package.json", async () => { + // The test validates the following dependency tree, when npm 3+ is used. + // + // ├── firstPackage@1.0.0 + // ├── secondPackage@1.1.0 + // └── thirdPackage@1.2.0 + + const getNativeScriptDataForPlugin = (pluginName: string): any => { + return { + platforms: { + "tns-android": "x.x.x", + "tns-ios": "x.x.x", + }, + + customPropertyUsedForThisTestOnly: pluginName, + }; + }; + + const rootDeps: IDependencyInfo[] = [ + generateDependency( + firstPackage, + "1.0.0", + 0, + null, + getNativeScriptDataForPlugin(firstPackage) + ), + generateDependency( + secondPackage, + "1.1.0", + 0, + null, + getNativeScriptDataForPlugin(secondPackage) + ), + generateDependency( + thirdPackage, + "1.2.0", + 0, + null, + getNativeScriptDataForPlugin(thirdPackage) + ), + ]; + + const nodeModulesDependenciesBuilder = generateTest(rootDeps); + const actualResult = await nodeModulesDependenciesBuilder.getProductionDependencies( + pathToProject + ); + + const expectedResult: IDependencyData[] = [ + getNodeModuleInfoForExpectedDependency( + firstPackage, + 0, + getNativeScriptDataForPlugin(firstPackage), + null, + null, + "1.0.0" + ), + getNodeModuleInfoForExpectedDependency( + secondPackage, + 0, + getNativeScriptDataForPlugin(secondPackage), + null, + null, + "1.1.0" + ), + getNodeModuleInfoForExpectedDependency( + thirdPackage, + 0, + getNativeScriptDataForPlugin(thirdPackage), + null, + null, + "1.2.0" + ), + ]; + + assert.deepStrictEqual(actualResult, expectedResult); + }); + + it("ignoring dependencies", async () => { + // The test validates the following dependency tree, when npm 3+ is used. + // + // ├── firstPackage@1.0.0 + // ├── secondPackage@1.1.0 + // └── thirdPackage@1.2.0 + + const rootDeps: IDependencyInfo[] = [ + generateDependency(firstPackage, "1.0.0", 0, null), + generateDependency(secondPackage, "1.1.0", 0, null), + generateDependency(thirdPackage, "1.2.0", 0, null), + ]; + + const nodeModulesDependenciesBuilder = generateTest(rootDeps); + const actualResult = await nodeModulesDependenciesBuilder.getProductionDependencies( + pathToProject, + [firstPackage] + ); + + const expectedResult: IDependencyData[] = [ + getNodeModuleInfoForExpectedDependency( + secondPackage, + 0, + null, + null, + null, + "1.1.0" + ), + getNodeModuleInfoForExpectedDependency( + thirdPackage, + 0, + null, + null, + null, + "1.2.0" + ), + ]; + + assert.deepStrictEqual(actualResult, expectedResult); + }); + }); + }); +});