diff --git a/Gruntfile.js b/Gruntfile.js index 26f6fffbe1..a08b50c319 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -2,7 +2,7 @@ const childProcess = require("child_process"); const EOL = require("os").EOL; const path = require("path"); const now = new Date().toISOString(); -const latestVersion = require('latest-version').default; +const manifest = require('pacote').manifest; const ENVIRONMENTS = { @@ -260,28 +260,26 @@ function registerTestingDependenciesTasks(grunt) { return done(false); } - - // Kick off all version resolutions in parallel - const versionPromises = testDependencies.map(dep => { - if (dep.version) { - dependenciesVersions[dep.name] = dep.version; - return Promise.resolve(); - } - return latestVersion(dep.name).then(v => { - dependenciesVersions[dep.name] = v; - }); - }); - - Promise.all(versionPromises) - .then(() => { - grunt.file.write(generatedVersionFilePath, JSON.stringify(dependenciesVersions, null, 2)); + (async () => { + try { + for (const dep of testDependencies) { + if (dep.version) { + dependenciesVersions[dep.name] = dep.version; + } else { + dependenciesVersions[dep.name] = await latestVersion(dep.name); + } + } + grunt.file.write( + generatedVersionFilePath, + JSON.stringify(dependenciesVersions, null, 2) + ); grunt.log.writeln("Wrote", generatedVersionFilePath); done(); - }) - .catch(err => { + } catch (err) { grunt.log.error(err); done(false); - }); + } + })(); }); grunt.registerTask("verify_unit_testing_dependencies", function () { @@ -291,3 +289,9 @@ function registerTestingDependenciesTasks(grunt) { }); } +async function latestVersion(name) { + // only fetches the package.json for the latest dist-tag + const { version } = await manifest(name.toLowerCase(), { fullMetadata: false }); + return version; +} + diff --git a/lib/bootstrap.ts b/lib/bootstrap.ts index ca8fe39888..ad181a1062 100644 --- a/lib/bootstrap.ts +++ b/lib/bootstrap.ts @@ -414,8 +414,8 @@ injector.require( injector.requirePublic("cleanupService", "./services/cleanup-service"); injector.require( - "webpackCompilerService", - "./services/webpack/webpack-compiler-service", + "bundlerCompilerService", + "./services/bundler/bundler-compiler-service", ); injector.require( diff --git a/lib/common/mobile/android/android-emulator-services.ts b/lib/common/mobile/android/android-emulator-services.ts index 8fd7cbb592..6a973d2ed4 100644 --- a/lib/common/mobile/android/android-emulator-services.ts +++ b/lib/common/mobile/android/android-emulator-services.ts @@ -8,7 +8,8 @@ import { injector } from "../../yok"; import * as semver from "semver"; export class AndroidEmulatorServices - implements Mobile.IEmulatorPlatformService { + implements Mobile.IEmulatorPlatformService +{ constructor( private $androidGenymotionService: Mobile.IAndroidVirtualDeviceService, private $androidVirtualDeviceService: Mobile.IAndroidVirtualDeviceService, @@ -16,71 +17,75 @@ export class AndroidEmulatorServices private $childProcess: IChildProcess, private $emulatorHelper: Mobile.IEmulatorHelper, private $logger: ILogger, - private $utils: IUtils + private $utils: IUtils, ) {} public async getEmulatorImages(): Promise { const adbDevicesOutput = await this.$adb.getDevicesSafe(); - const avdAvailableEmulatorsOutput = await this.$androidVirtualDeviceService.getEmulatorImages( - adbDevicesOutput - ); - const genyAvailableDevicesOutput = await this.$androidGenymotionService.getEmulatorImages( - adbDevicesOutput - ); + const avdAvailableEmulatorsOutput = + await this.$androidVirtualDeviceService.getEmulatorImages( + adbDevicesOutput, + ); + const genyAvailableDevicesOutput = + await this.$androidGenymotionService.getEmulatorImages(adbDevicesOutput); const devices = _.concat( avdAvailableEmulatorsOutput.devices, - genyAvailableDevicesOutput.devices + genyAvailableDevicesOutput.devices, ).filter((item) => !!item); return { devices, errors: avdAvailableEmulatorsOutput.errors.concat( - genyAvailableDevicesOutput.errors + genyAvailableDevicesOutput.errors, ), }; } public async getRunningEmulatorIds(): Promise { const adbDevicesOutput = await this.$adb.getDevicesSafe(); - const avds = await this.$androidVirtualDeviceService.getRunningEmulatorIds( - adbDevicesOutput - ); - const genies = await this.$androidGenymotionService.getRunningEmulatorIds( - adbDevicesOutput - ); + const avds = + await this.$androidVirtualDeviceService.getRunningEmulatorIds( + adbDevicesOutput, + ); + const genies = + await this.$androidGenymotionService.getRunningEmulatorIds( + adbDevicesOutput, + ); return avds.concat(genies); } public async getRunningEmulatorName(emulatorId: string): Promise { - let result = await this.$androidVirtualDeviceService.getRunningEmulatorName( - emulatorId - ); - if (!result) { - result = await this.$androidGenymotionService.getRunningEmulatorName( - emulatorId + let result = + await this.$androidVirtualDeviceService.getRunningEmulatorName( + emulatorId, ); + if (!result) { + result = + await this.$androidGenymotionService.getRunningEmulatorName(emulatorId); } return result; } public async getRunningEmulatorImageIdentifier( - emulatorId: string + emulatorId: string, ): Promise { - let result = await this.$androidVirtualDeviceService.getRunningEmulatorImageIdentifier( - emulatorId - ); - if (!result) { - result = await this.$androidGenymotionService.getRunningEmulatorImageIdentifier( - emulatorId + let result = + await this.$androidVirtualDeviceService.getRunningEmulatorImageIdentifier( + emulatorId, ); + if (!result) { + result = + await this.$androidGenymotionService.getRunningEmulatorImageIdentifier( + emulatorId, + ); } return result; } public async startEmulator( - options: Mobile.IAndroidStartEmulatorOptions + options: Mobile.IAndroidStartEmulatorOptions, ): Promise { const output = await this.startEmulatorCore(options); let bootToCompleteOutput = null; @@ -88,13 +93,13 @@ export class AndroidEmulatorServices bootToCompleteOutput = await this.waitForEmulatorBootToComplete( output.runningEmulator, output.endTimeEpoch, - options.timeout + options.timeout, ); } return { errors: ((output && output.errors) || []).concat( - (bootToCompleteOutput && bootToCompleteOutput.errors) || [] + (bootToCompleteOutput && bootToCompleteOutput.errors) || [], ), }; } @@ -104,7 +109,7 @@ export class AndroidEmulatorServices } private async startEmulatorCore( - options: Mobile.IAndroidStartEmulatorOptions + options: Mobile.IAndroidStartEmulatorOptions, ): Promise<{ runningEmulator: Mobile.IDeviceInfo; errors: string[]; @@ -118,7 +123,7 @@ export class AndroidEmulatorServices let emulator = this.$emulatorHelper.getEmulatorByStartEmulatorOptions( options, - availableEmulators + availableEmulators, ); if ( !emulator && @@ -158,7 +163,7 @@ export class AndroidEmulatorServices const emulators = (await this.getEmulatorImages()).devices; const newEmulator = _.find( emulators, - (e) => e.imageIdentifier === emulator.imageIdentifier + (e) => e.imageIdentifier === emulator.imageIdentifier, ); if (newEmulator && this.$emulatorHelper.isEmulatorRunning(newEmulator)) { return { @@ -185,29 +190,29 @@ export class AndroidEmulatorServices let pathToEmulatorExecutable = null; let startEmulatorArgs = null; if (emulator.vendor === AndroidVirtualDevice.AVD_VENDOR_NAME) { - pathToEmulatorExecutable = this.$androidVirtualDeviceService - .pathToEmulatorExecutable; + pathToEmulatorExecutable = + this.$androidVirtualDeviceService.pathToEmulatorExecutable; startEmulatorArgs = this.$androidVirtualDeviceService.startEmulatorArgs( - emulator.imageIdentifier + emulator.imageIdentifier, ); } else if ( emulator.vendor === AndroidVirtualDevice.GENYMOTION_VENDOR_NAME ) { - pathToEmulatorExecutable = this.$androidGenymotionService - .pathToEmulatorExecutable; + pathToEmulatorExecutable = + this.$androidGenymotionService.pathToEmulatorExecutable; startEmulatorArgs = this.$androidGenymotionService.startEmulatorArgs( - emulator.imageIdentifier + emulator.imageIdentifier, ); } this.$logger.info( - `Starting Android emulator with image ${emulator.imageIdentifier}` + `Starting Android emulator with image ${emulator.imageIdentifier}`, ); const childProcess = this.$childProcess.spawn( pathToEmulatorExecutable, startEmulatorArgs, - { stdio: "ignore", detached: true } + { stdio: "ignore", detached: true }, ); childProcess.unref(); childProcess.on("error", (err: Error) => { @@ -241,7 +246,7 @@ export class AndroidEmulatorServices private async waitForEmulatorBootToComplete( emulator: Mobile.IDeviceInfo, endTimeEpoch: number, - timeout: number + timeout: number, ): Promise<{ runningEmulator: Mobile.IDeviceInfo; errors: string[] }> { this.$logger.info("Waiting for emulator device initialization...", { [LoggerConfigData.skipNewLine]: true, @@ -249,11 +254,11 @@ export class AndroidEmulatorServices const isInfiniteWait = this.$utils.getMilliSecondsTimeout( - timeout || AndroidVirtualDevice.TIMEOUT_SECONDS + timeout || AndroidVirtualDevice.TIMEOUT_SECONDS, ) === 0; while (getCurrentEpochTime() < endTimeEpoch || isInfiniteWait) { const isEmulatorBootCompleted = await this.isEmulatorBootCompleted( - emulator.identifier + emulator.identifier, ); if (isEmulatorBootCompleted) { this.$logger.info(EOL, { [LoggerConfigData.skipNewLine]: true }); @@ -276,7 +281,7 @@ export class AndroidEmulatorServices private async isEmulatorBootCompleted(emulatorId: string): Promise { const output = await this.$adb.getPropertyValue( emulatorId, - "dev.bootcomplete" + "dev.bootcomplete", ); const matches = output.match("1"); return matches && matches.length > 0; diff --git a/lib/common/mobile/emulator-helper.ts b/lib/common/mobile/emulator-helper.ts index 9e8434cba7..baf98fc7aa 100644 --- a/lib/common/mobile/emulator-helper.ts +++ b/lib/common/mobile/emulator-helper.ts @@ -28,7 +28,7 @@ export class EmulatorHelper implements Mobile.IEmulatorHelper { }; public getEmulatorsFromAvailableEmulatorsOutput( - availableEmulatorsOutput: Mobile.IListEmulatorsOutput + availableEmulatorsOutput: Mobile.IListEmulatorsOutput, ): Mobile.IDeviceInfo[] { return _(availableEmulatorsOutput) .valuesIn() @@ -39,7 +39,7 @@ export class EmulatorHelper implements Mobile.IEmulatorHelper { } public getErrorsFromAvailableEmulatorsOutput( - availableEmulatorsOutput: Mobile.IListEmulatorsOutput + availableEmulatorsOutput: Mobile.IListEmulatorsOutput, ): string[] { return _(availableEmulatorsOutput) .valuesIn() @@ -51,7 +51,7 @@ export class EmulatorHelper implements Mobile.IEmulatorHelper { public getEmulatorByImageIdentifier( imageIdentifier: string, - emulators: Mobile.IDeviceInfo[] + emulators: Mobile.IDeviceInfo[], ): Mobile.IDeviceInfo { const imagerIdentifierLowerCase = imageIdentifier && imageIdentifier.toLowerCase(); @@ -61,13 +61,13 @@ export class EmulatorHelper implements Mobile.IEmulatorHelper { emulator && emulator.imageIdentifier && imageIdentifier && - emulator.imageIdentifier.toLowerCase() === imagerIdentifierLowerCase + emulator.imageIdentifier.toLowerCase() === imagerIdentifierLowerCase, ); } public getEmulatorByIdOrName( emulatorIdOrName: string, - emulators: Mobile.IDeviceInfo[] + emulators: Mobile.IDeviceInfo[], ): Mobile.IDeviceInfo { const emulatorIdOrNameLowerCase = emulatorIdOrName && emulatorIdOrName.toLowerCase(); @@ -78,7 +78,7 @@ export class EmulatorHelper implements Mobile.IEmulatorHelper { emulatorIdOrNameLowerCase && ((emulator.identifier && emulator.identifier.toLowerCase() === emulatorIdOrNameLowerCase) || - emulator.displayName.toLowerCase() === emulatorIdOrNameLowerCase) + emulator.displayName.toLowerCase() === emulatorIdOrNameLowerCase), ); } @@ -88,7 +88,7 @@ export class EmulatorHelper implements Mobile.IEmulatorHelper { public getEmulatorByStartEmulatorOptions( options: Mobile.IStartEmulatorOptions, - emulators: Mobile.IDeviceInfo[] + emulators: Mobile.IDeviceInfo[], ): Mobile.IDeviceInfo { let result: Mobile.IDeviceInfo = null; @@ -99,7 +99,7 @@ export class EmulatorHelper implements Mobile.IEmulatorHelper { if (!result && options.imageIdentifier) { result = this.getEmulatorByImageIdentifier( options.imageIdentifier, - emulators + emulators, ); } @@ -112,7 +112,7 @@ export class EmulatorHelper implements Mobile.IEmulatorHelper { public setRunningAndroidEmulatorProperties( emulatorId: string, - emulator: Mobile.IDeviceInfo + emulator: Mobile.IDeviceInfo, ): void { emulator.identifier = emulatorId; emulator.status = RUNNING_EMULATOR_STATUS; diff --git a/lib/constants.ts b/lib/constants.ts index 0ac436ec77..c76d6f6696 100644 --- a/lib/constants.ts +++ b/lib/constants.ts @@ -16,6 +16,7 @@ export const SCOPED_TNS_CORE_MODULES = "@nativescript/core"; export const TNS_CORE_THEME_NAME = "nativescript-theme-core"; export const SCOPED_TNS_CORE_THEME_NAME = "@nativescript/theme"; export const WEBPACK_PLUGIN_NAME = "@nativescript/webpack"; +export const RSPACK_PLUGIN_NAME = "@nativescript/rspack"; export const TNS_CORE_MODULES_WIDGETS_NAME = "tns-core-modules-widgets"; export const UI_MOBILE_BASE_NAME = "@nativescript/ui-mobile-base"; export const TNS_ANDROID_RUNTIME_NAME = "tns-android"; @@ -36,6 +37,7 @@ export const XML_FILE_EXTENSION = ".xml"; export const PLATFORMS_DIR_NAME = "platforms"; export const HOOKS_DIR_NAME = "hooks"; export const WEBPACK_CONFIG_NAME = "webpack.config.js"; +export const RSPACK_CONFIG_NAME = "rspack.config.js"; export const TSCCONFIG_TNS_JSON_NAME = "tsconfig.tns.json"; export const KARMA_CONFIG_NAME = "karma.conf.js"; export const LIB_DIR_NAME = "lib"; @@ -222,7 +224,7 @@ export const CACACHE_DIRECTORY_NAME = "_cacache"; export const FILES_CHANGE_EVENT_NAME = "filesChangeEvent"; export const INITIAL_SYNC_EVENT_NAME = "initialSyncEvent"; export const PREPARE_READY_EVENT_NAME = "prepareReadyEvent"; -export const WEBPACK_COMPILATION_COMPLETE = "webpackCompilationComplete"; +export const BUNDLER_COMPILATION_COMPLETE = "bundlerCompilationComplete"; export class DebugCommandErrors { public static UNABLE_TO_USE_FOR_DEVICE_AND_EMULATOR = diff --git a/lib/controllers/prepare-controller.ts b/lib/controllers/prepare-controller.ts index 8ba3550b86..73fa9ab5b3 100644 --- a/lib/controllers/prepare-controller.ts +++ b/lib/controllers/prepare-controller.ts @@ -13,6 +13,7 @@ import { hook } from "../common/helpers"; import { injector } from "../common/yok"; import { AnalyticsEventLabelDelimiter, + BUNDLER_COMPILATION_COMPLETE, CONFIG_FILE_NAME_JS, CONFIG_FILE_NAME_TS, PACKAGE_JSON_FILE_NAME, @@ -20,7 +21,6 @@ import { PREPARE_READY_EVENT_NAME, SupportedPlatform, TrackActionNames, - WEBPACK_COMPILATION_COMPLETE, } from "../constants"; import { IOptions, IWatchIgnoreListService } from "../declarations"; import { @@ -67,7 +67,7 @@ export class PrepareController extends EventEmitter { private $prepareNativePlatformService: IPrepareNativePlatformService, private $projectChangesService: IProjectChangesService, private $projectDataService: IProjectDataService, - private $webpackCompilerService: IWebpackCompilerService, + private $bundlerCompilerService: IBundlerCompilerService, private $watchIgnoreListService: IWatchIgnoreListService, private $analyticsService: IAnalyticsService, private $markingModeService: IMarkingModeService, @@ -117,9 +117,9 @@ export class PrepareController extends EventEmitter { this.watchersData[projectDir][platformLowerCase] && this.watchersData[projectDir][platformLowerCase].hasWebpackCompilerProcess ) { - await this.$webpackCompilerService.stopWebpackCompiler(platformLowerCase); - this.$webpackCompilerService.removeListener( - WEBPACK_COMPILATION_COMPLETE, + await this.$bundlerCompilerService.stopBundlerCompiler(platformLowerCase); + this.$bundlerCompilerService.removeListener( + BUNDLER_COMPILATION_COMPLETE, this.webpackCompilerHandler, ); this.watchersData[projectDir][ @@ -177,7 +177,7 @@ export class PrepareController extends EventEmitter { prepareData, ); } else { - await this.$webpackCompilerService.compileWithoutWatch( + await this.$bundlerCompilerService.compileWithoutWatch( platformData, projectData, prepareData, @@ -296,15 +296,15 @@ export class PrepareController extends EventEmitter { }; this.webpackCompilerHandler = handler.bind(this); - this.$webpackCompilerService.on( - WEBPACK_COMPILATION_COMPLETE, + this.$bundlerCompilerService.on( + BUNDLER_COMPILATION_COMPLETE, this.webpackCompilerHandler, ); this.watchersData[projectData.projectDir][ platformData.platformNameLowerCase ].hasWebpackCompilerProcess = true; - await this.$webpackCompilerService.compileWithWatch( + await this.$bundlerCompilerService.compileWithWatch( platformData, projectData, prepareData, @@ -560,7 +560,7 @@ export class PrepareController extends EventEmitter { if (this.pausedFileWatch) { for (const watcher of watchers) { for (const platform in watcher) { - await this.$webpackCompilerService.stopWebpackCompiler(platform); + await this.$bundlerCompilerService.stopBundlerCompiler(platform); watcher[platform].hasWebpackCompilerProcess = false; } } @@ -569,7 +569,7 @@ export class PrepareController extends EventEmitter { for (const platform in watcher) { const args = watcher[platform].prepareArguments; watcher[platform].hasWebpackCompilerProcess = true; - await this.$webpackCompilerService.compileWithWatch( + await this.$bundlerCompilerService.compileWithWatch( args.platformData, args.projectData, args.prepareData, diff --git a/lib/definitions/project.d.ts b/lib/definitions/project.d.ts index e99e204691..c8046b2ed4 100644 --- a/lib/definitions/project.d.ts +++ b/lib/definitions/project.d.ts @@ -121,6 +121,7 @@ export interface IOSLocalSPMPackage extends IOSSPMPackageBase { } export type IOSSPMPackage = IOSRemoteSPMPackage | IOSLocalSPMPackage; +export type BundlerType = "webpack" | "rspack" | "vite"; interface INsConfigIOS extends INsConfigPlaform { discardUncaughtJsExceptions?: boolean; @@ -182,6 +183,8 @@ interface INsConfig { shared?: boolean; overridePods?: string; webpackConfigPath?: string; + bundlerConfigPath?: string; + bundler?: BundlerType; ios?: INsConfigIOS; android?: INsConfigAndroid; visionos?: INSConfigVisionOS; @@ -215,13 +218,28 @@ interface IProjectData extends ICreateProjectData { * Value is true when project has nativescript.config and it has `shared: true` in it. */ isShared: boolean; - /** + * Specifies the bundler used to build the application. + * + * - `"webpack"`: Uses Webpack for traditional bundling. + * - `"rspack"`: Uses Rspack for fast bundling. + * - `"vite"`: Uses Vite for fast bundling. + * + * @default "webpack" + */ + bundler: BundlerType; + /** + * @deprecated Use bundlerConfigPath * Defines the path to the configuration file passed to webpack process. * By default this is the webpack.config.js at the root of the application. * The value can be changed by setting `webpackConfigPath` in nativescript.config. */ webpackConfigPath: string; + /** + * Defines the path to the bundler configuration file passed to the compiler. + * The value can be changed by setting `bundlerConfigPath` in nativescript.config. + */ + bundlerConfigPath: string; projectName: string; /** diff --git a/lib/project-data.ts b/lib/project-data.ts index 3e36c374b5..277dbf32d1 100644 --- a/lib/project-data.ts +++ b/lib/project-data.ts @@ -5,6 +5,7 @@ import { parseJson } from "./common/helpers"; import { EOL } from "os"; import { cache } from "./common/decorators"; import { + BundlerType, INsConfig, IProjectConfigService, IProjectData, @@ -99,6 +100,8 @@ export class ProjectData implements IProjectData { public podfilePath: string; public isShared: boolean; public webpackConfigPath: string; + public bundlerConfigPath: string; + public bundler: BundlerType; public initialized: boolean; constructor( @@ -110,7 +113,7 @@ export class ProjectData implements IProjectData { private $logger: ILogger, private $injector: IInjector, private $androidResourcesMigrationService: IAndroidResourcesMigrationService, - private $devicePlatformsConstants: Mobile.IDevicePlatformsConstants + private $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, ) {} get projectConfig(): IProjectConfigService { @@ -142,7 +145,7 @@ export class ProjectData implements IProjectData { public initializeProjectDataFromContent( packageJsonContent: string, - projectDir?: string + projectDir?: string, ): void { projectDir = projectDir || this.$projectHelper.projectDir || ""; this.projectDir = projectDir; @@ -157,7 +160,7 @@ export class ProjectData implements IProjectData { this.$errors.fail( `The project file ${this.projectFilePath} is corrupted. ${EOL}` + `Consider restoring an earlier version from your source control or backup.${EOL}` + - `Additional technical info: ${err.toString()}` + `Additional technical info: ${err.toString()}`, ); } @@ -178,36 +181,43 @@ export class ProjectData implements IProjectData { this.appDirectoryPath = this.getAppDirectoryPath(); this.appResourcesDirectoryPath = this.getAppResourcesDirectoryPath(); this.androidManifestPath = this.getPathToAndroidManifest( - this.appResourcesDirectoryPath + this.appResourcesDirectoryPath, ); this.gradleFilesDirectoryPath = path.join( this.appResourcesDirectoryPath, - this.$devicePlatformsConstants.Android + this.$devicePlatformsConstants.Android, ); this.appGradlePath = path.join( this.gradleFilesDirectoryPath, - constants.APP_GRADLE_FILE_NAME + constants.APP_GRADLE_FILE_NAME, ); this.infoPlistPath = path.join( this.appResourcesDirectoryPath, this.$devicePlatformsConstants.iOS, - constants.INFO_PLIST_FILE_NAME + constants.INFO_PLIST_FILE_NAME, ); this.buildXcconfigPath = path.join( this.appResourcesDirectoryPath, this.$devicePlatformsConstants.iOS, - constants.BUILD_XCCONFIG_FILE_NAME + constants.BUILD_XCCONFIG_FILE_NAME, ); this.podfilePath = path.join( this.appResourcesDirectoryPath, this.$devicePlatformsConstants.iOS, - constants.PODFILE_NAME + constants.PODFILE_NAME, ); this.isShared = !!(this.nsConfig && this.nsConfig.shared); - this.webpackConfigPath = + + const webpackConfigPath = this.nsConfig && this.nsConfig.webpackConfigPath ? path.resolve(this.projectDir, this.nsConfig.webpackConfigPath) : path.join(this.projectDir, "webpack.config.js"); + this.webpackConfigPath = webpackConfigPath; + this.bundlerConfigPath = + this.nsConfig && this.nsConfig.bundlerConfigPath + ? path.resolve(this.projectDir, this.nsConfig.bundlerConfigPath) + : webpackConfigPath; + this.bundler = this?.nsConfig?.bundler ?? "webpack"; return; } @@ -217,7 +227,7 @@ export class ProjectData implements IProjectData { private getPathToAndroidManifest(appResourcesDir: string): string { const androidDirPath = path.join( appResourcesDir, - this.$devicePlatformsConstants.Android + this.$devicePlatformsConstants.Android, ); const androidManifestDir = this.$androidResourcesMigrationService.hasMigrated(appResourcesDir) @@ -230,13 +240,13 @@ export class ProjectData implements IProjectData { private errorInvalidProject(projectDir: string): void { const currentDir = path.resolve("."); this.$logger.trace( - `Unable to find project. projectDir: ${projectDir}, options.path: ${this.$options.path}, ${currentDir}` + `Unable to find project. projectDir: ${projectDir}, options.path: ${this.$options.path}, ${currentDir}`, ); // This is the case when no project file found this.$errors.fail( "No project found at or above '%s' and neither was a --path specified.", - projectDir || this.$options.path || currentDir + projectDir || this.$options.path || currentDir, ); } @@ -291,7 +301,7 @@ export class ProjectData implements IProjectData { private resolveToProjectDir( pathToResolve: string, - projectDir?: string + projectDir?: string, ): string { if (!projectDir) { projectDir = this.projectDir; @@ -306,7 +316,7 @@ export class ProjectData implements IProjectData { @cache() private initializeProjectIdentifiers( - config: INsConfig + config: INsConfig, ): Mobile.IProjectIdentifier { this.$logger.trace(`Initializing project identifiers. Config: `, config); @@ -341,18 +351,18 @@ export class ProjectData implements IProjectData { private getProjectType(): string { let detectedProjectType = _.find( ProjectData.PROJECT_TYPES, - (projectType) => projectType.isDefaultProjectType + (projectType) => projectType.isDefaultProjectType, ).type; const deps: string[] = _.keys(this.dependencies).concat( - _.keys(this.devDependencies) + _.keys(this.devDependencies), ); _.each(ProjectData.PROJECT_TYPES, (projectType) => { if ( _.some( projectType.requiredDependencies, - (requiredDependency) => deps.indexOf(requiredDependency) !== -1 + (requiredDependency) => deps.indexOf(requiredDependency) !== -1, ) ) { detectedProjectType = projectType.type; @@ -366,7 +376,7 @@ export class ProjectData implements IProjectData { @cache() private warnProjectId(): void { this.$logger.warn( - "[WARNING]: IProjectData.projectId is deprecated. Please use IProjectData.projectIdentifiers[platform]." + "[WARNING]: IProjectData.projectId is deprecated. Please use IProjectData.projectIdentifiers[platform].", ); } } diff --git a/lib/services/webpack/webpack-compiler-service.ts b/lib/services/bundler/bundler-compiler-service.ts similarity index 68% rename from lib/services/webpack/webpack-compiler-service.ts rename to lib/services/bundler/bundler-compiler-service.ts index 5a571b7168..421808dfdc 100644 --- a/lib/services/webpack/webpack-compiler-service.ts +++ b/lib/services/bundler/bundler-compiler-service.ts @@ -5,8 +5,8 @@ import * as _ from "lodash"; import { EventEmitter } from "events"; import { performanceLog } from "../../common/decorators"; import { - WEBPACK_COMPILATION_COMPLETE, WEBPACK_PLUGIN_NAME, + BUNDLER_COMPILATION_COMPLETE, PackageManagers, CONFIG_FILE_NAME_DISPLAY, } from "../../constants"; @@ -16,7 +16,11 @@ import { IOptions, } from "../../declarations"; import { IPlatformData } from "../../definitions/platform"; -import { IProjectData } from "../../definitions/project"; +import { + BundlerType, + IProjectConfigService, + IProjectData, +} from "../../definitions/project"; import { IDictionary, IErrors, @@ -34,23 +38,23 @@ import { } from "../../helpers/package-path-helper"; // todo: move out of here -interface IWebpackMessage { +interface IBundlerMessage { type: "compilation" | "hmr-status"; version?: number; hash?: string; data?: T; } -interface IWebpackCompilation { +interface IBundlerCompilation { emittedAssets: string[]; staleAssets: string[]; } -export class WebpackCompilerService +export class BundlerCompilerService extends EventEmitter - implements IWebpackCompilerService + implements IBundlerCompilerService { - private webpackProcesses: IDictionary = {}; + private bundlerProcesses: IDictionary = {}; private expectedHashes: IStringDictionary = {}; constructor( @@ -65,6 +69,7 @@ export class WebpackCompilerService private $cleanupService: ICleanupService, private $packageManager: IPackageManager, private $packageInstallationManager: IPackageInstallationManager, // private $sharedEventBus: ISharedEventBus + private $projectConfigService: IProjectConfigService, ) { super(); } @@ -75,15 +80,15 @@ export class WebpackCompilerService prepareData: IPrepareData, ): Promise { return new Promise(async (resolve, reject) => { - if (this.webpackProcesses[platformData.platformNameLowerCase]) { + if (this.bundlerProcesses[platformData.platformNameLowerCase]) { resolve(void 0); return; } - let isFirstWebpackWatchCompilation = true; + let isFirstBundlerWatchCompilation = true; prepareData.watch = true; try { - const childProcess = await this.startWebpackProcess( + const childProcess = await this.startBundleProcess( platformData, projectData, prepareData, @@ -97,8 +102,8 @@ export class WebpackCompilerService process.stderr.write(data); }); - childProcess.on("message", (message: string | IWebpackEmitMessage) => { - this.$logger.trace("Message from webpack", message); + childProcess.on("message", (message: string | IBundlerEmitMessage) => { + this.$logger.trace(`Message from ${projectData.bundler}`, message); // if we are on webpack5 - we handle HMR in a slightly different way if ( @@ -108,8 +113,8 @@ export class WebpackCompilerService ) { // first compilation can be ignored because it will be synced regardless // handling it here would trigger 2 syncs - if (isFirstWebpackWatchCompilation) { - isFirstWebpackWatchCompilation = false; + if (isFirstBundlerWatchCompilation) { + isFirstBundlerWatchCompilation = false; resolve(childProcess); return; } @@ -122,22 +127,27 @@ export class WebpackCompilerService // } return this.handleHMRMessage( - message as IWebpackMessage, + message as IBundlerMessage, platformData, projectData, prepareData, ); } - if (message === "Webpack compilation complete.") { - this.$logger.info("Webpack build done!"); + if ( + message === + `${capitalizeFirstLetter(projectData.bundler)} compilation complete.` + ) { + this.$logger.info( + `${capitalizeFirstLetter(projectData.bundler)} build done!`, + ); resolve(childProcess); } - message = message as IWebpackEmitMessage; + message = message as IBundlerEmitMessage; if (message.emittedFiles) { - if (isFirstWebpackWatchCompilation) { - isFirstWebpackWatchCompilation = false; + if (isFirstBundlerWatchCompilation) { + isFirstBundlerWatchCompilation = false; this.expectedHashes[platformData.platformNameLowerCase] = prepareData.hmr ? message.hash : ""; return; @@ -189,7 +199,10 @@ export class WebpackCompilerService platform: platformData.platformNameLowerCase, }; - this.$logger.trace("Generated data from webpack message:", data); + this.$logger.trace( + `Generated data from ${projectData.bundler} message:`, + data, + ); // the hash of the compilation is the same as the previous one and there are only hot updates produced if (data.hasOnlyHotUpdateFiles && previousHash === message.hash) { @@ -197,33 +210,50 @@ export class WebpackCompilerService } if (data.files.length) { - this.emit(WEBPACK_COMPILATION_COMPLETE, data); + this.emit(BUNDLER_COMPILATION_COMPLETE, data); } } }); childProcess.on("error", (err) => { this.$logger.trace( - `Unable to start webpack process in watch mode. Error is: ${err}`, + `Unable to start ${projectData.bundler} process in watch mode. Error is: ${err}`, ); - delete this.webpackProcesses[platformData.platformNameLowerCase]; + delete this.bundlerProcesses[platformData.platformNameLowerCase]; reject(err); }); childProcess.on("close", async (arg: any) => { - await this.$cleanupService.removeKillProcess( - childProcess.pid.toString(), - ); - const exitCode = typeof arg === "number" ? arg : arg && arg.code; this.$logger.trace( - `Webpack process exited with code ${exitCode} when we expected it to be long living with watch.`, + `${capitalizeFirstLetter(projectData.bundler)} process exited with code ${exitCode} when we expected it to be long living with watch.`, + ); + if (this.getBundler() === "vite" && exitCode === 0) { + // note experimental: investigate watch mode + const bundlePath = path.join( + projectData.projectDir, + "dist/bundle.js", + ); + console.log("bundlePath:", bundlePath); + const data = { + files: [bundlePath], + hasOnlyHotUpdateFiles: false, + hmrData: {}, + platform: platformData.platformNameLowerCase, + }; + this.emit(BUNDLER_COMPILATION_COMPLETE, data); + resolve(1); + return; + } + + await this.$cleanupService.removeKillProcess( + childProcess.pid.toString(), ); const error: any = new Error( - `Executing webpack failed with exit code ${exitCode}.`, + `Executing ${projectData.bundler} failed with exit code ${exitCode}.`, ); error.code = exitCode; - delete this.webpackProcesses[platformData.platformNameLowerCase]; + delete this.bundlerProcesses[platformData.platformNameLowerCase]; reject(error); }); } catch (err) { @@ -238,13 +268,13 @@ export class WebpackCompilerService prepareData: IPrepareData, ): Promise { return new Promise(async (resolve, reject) => { - if (this.webpackProcesses[platformData.platformNameLowerCase]) { + if (this.bundlerProcesses[platformData.platformNameLowerCase]) { resolve(); return; } try { - const childProcess = await this.startWebpackProcess( + const childProcess = await this.startBundleProcess( platformData, projectData, prepareData, @@ -252,9 +282,9 @@ export class WebpackCompilerService childProcess.on("error", (err) => { this.$logger.trace( - `Unable to start webpack process in non-watch mode. Error is: ${err}`, + `Unable to start ${projectData.bundler} process in non-watch mode. Error is: ${err}`, ); - delete this.webpackProcesses[platformData.platformNameLowerCase]; + delete this.bundlerProcesses[platformData.platformNameLowerCase]; reject(err); }); @@ -263,13 +293,13 @@ export class WebpackCompilerService childProcess.pid.toString(), ); - delete this.webpackProcesses[platformData.platformNameLowerCase]; + delete this.bundlerProcesses[platformData.platformNameLowerCase]; const exitCode = typeof arg === "number" ? arg : arg && arg.code; if (exitCode === 0) { resolve(); } else { const error: any = new Error( - `Executing webpack failed with exit code ${exitCode}.`, + `Executing ${projectData.bundler} failed with exit code ${exitCode}.`, ); error.code = exitCode; reject(error); @@ -281,14 +311,14 @@ export class WebpackCompilerService }); } - public async stopWebpackCompiler(platform: string): Promise { + public async stopBundlerCompiler(platform: string): Promise { if (platform) { - await this.stopWebpackForPlatform(platform); + await this.stopBundlerForPlatform(platform); } else { - const webpackedPlatforms = Object.keys(this.webpackProcesses); + const bundlerPlatforms = Object.keys(this.bundlerProcesses); - for (let i = 0; i < webpackedPlatforms.length; i++) { - await this.stopWebpackForPlatform(webpackedPlatforms[i]); + for (let i = 0; i < bundlerPlatforms.length; i++) { + await this.stopBundlerForPlatform(bundlerPlatforms[i]); } } } @@ -304,15 +334,23 @@ export class WebpackCompilerService } @performanceLog() - private async startWebpackProcess( + private async startBundleProcess( platformData: IPlatformData, projectData: IProjectData, prepareData: IPrepareData, ): Promise { - if (!this.$fs.exists(projectData.webpackConfigPath)) { - this.$errors.fail( - `The webpack configuration file ${projectData.webpackConfigPath} does not exist. Ensure the file exists, or update the path in ${CONFIG_FILE_NAME_DISPLAY}.`, - ); + if (projectData.bundlerConfigPath) { + if (!this.$fs.exists(projectData.bundlerConfigPath)) { + this.$errors.fail( + `The bundler configuration file ${projectData.bundlerConfigPath} does not exist. Ensure the file exists, or update the path in ${CONFIG_FILE_NAME_DISPLAY}.`, + ); + } + } else { + if (!this.$fs.exists(projectData.bundlerConfigPath)) { + this.$errors.fail( + `The ${projectData.bundler} configuration file ${projectData.bundlerConfigPath} does not exist. Ensure the file exists, or update the path in ${CONFIG_FILE_NAME_DISPLAY}.`, + ); + } } const envData = this.buildEnvData( @@ -320,12 +358,17 @@ export class WebpackCompilerService projectData, prepareData, ); - const envParams = await this.buildEnvCommandLineParams( + const isVite = this.getBundler() === "vite"; + const cliArgs = await this.buildEnvCommandLineParams( envData, platformData, projectData, prepareData, ); + // Note: With Vite, we need `--` to prevent vite cli from erroring on unknown options. + const envParams = isVite + ? [`--mode=${platformData.platformNameLowerCase}`, "--", ...cliArgs] + : cliArgs; const additionalNodeArgs = semver.major(process.version) <= 8 ? ["--harmony"] : []; @@ -339,14 +382,16 @@ export class WebpackCompilerService const args = [ ...additionalNodeArgs, - this.getWebpackExecutablePath(projectData), - this.isWebpack5(projectData) ? `build` : null, - `--config=${projectData.webpackConfigPath}`, + this.getBundlerExecutablePath(projectData), + this.isModernBundler(projectData) ? `build` : null, + `--config=${projectData.bundlerConfigPath}`, ...envParams, ].filter(Boolean); - if (prepareData.watch) { - args.push("--watch"); + if (!isVite) { + if (prepareData.watch) { + args.push("--watch"); + } } const stdio = prepareData.watch ? ["ipc"] : "inherit"; @@ -357,6 +402,7 @@ export class WebpackCompilerService options.env = { ...process.env, NATIVESCRIPT_WEBPACK_ENV: JSON.stringify(envData), + NATIVESCRIPT_BUNDLER_ENV: JSON.stringify(envData), }; if (this.$hostInfo.isWindows) { Object.assign(options.env, { APPDATA: process.env.appData }); @@ -370,13 +416,15 @@ export class WebpackCompilerService }); } + console.log("args:", args); + const childProcess = this.$childProcess.spawn( process.execPath, args, options, ); - this.webpackProcesses[platformData.platformNameLowerCase] = childProcess; + this.bundlerProcesses[platformData.platformNameLowerCase] = childProcess; await this.$cleanupService.addKillProcess(childProcess.pid.toString()); return childProcess; @@ -467,23 +515,26 @@ export class WebpackCompilerService ); envFlagNames.splice(envFlagNames.indexOf("snapshot"), 1); } else if (this.$hostInfo.isWindows) { - const minWebpackPluginWithWinSnapshotsVersion = "1.3.0"; - const installedWebpackPluginVersion = - await this.$packageInstallationManager.getInstalledDependencyVersion( - WEBPACK_PLUGIN_NAME, - projectData.projectDir, - ); - const hasWebpackPluginWithWinSnapshotsSupport = - !!installedWebpackPluginVersion - ? semver.gte( - semver.coerce(installedWebpackPluginVersion), - minWebpackPluginWithWinSnapshotsVersion, - ) - : true; - if (!hasWebpackPluginWithWinSnapshotsSupport) { - this.$errors.fail( - `In order to generate Snapshots on Windows, please upgrade your Webpack plugin version (npm i ${WEBPACK_PLUGIN_NAME}@latest).`, - ); + if (projectData.bundler === "webpack") { + //TODO: check this use case for webpack5 WEBPACK_PLUGIN_NAME + const minWebpackPluginWithWinSnapshotsVersion = "1.3.0"; + const installedWebpackPluginVersion = + await this.$packageInstallationManager.getInstalledDependencyVersion( + WEBPACK_PLUGIN_NAME, + projectData.projectDir, + ); + const hasWebpackPluginWithWinSnapshotsSupport = + !!installedWebpackPluginVersion + ? semver.gte( + semver.coerce(installedWebpackPluginVersion), + minWebpackPluginWithWinSnapshotsVersion, + ) + : true; + if (!hasWebpackPluginWithWinSnapshotsSupport) { + this.$errors.fail( + `In order to generate Snapshots on Windows, please upgrade your Webpack plugin version (npm i ${WEBPACK_PLUGIN_NAME}@latest).`, + ); + } } } } @@ -562,30 +613,37 @@ export class WebpackCompilerService return hotHash || ""; } - private async stopWebpackForPlatform(platform: string) { - this.$logger.trace(`Stopping webpack watch for platform ${platform}.`); - const webpackProcess = this.webpackProcesses[platform]; - await this.$cleanupService.removeKillProcess(webpackProcess.pid.toString()); - if (webpackProcess) { - webpackProcess.kill("SIGINT"); - delete this.webpackProcesses[platform]; + private async stopBundlerForPlatform(platform: string) { + this.$logger.trace( + `Stopping ${this.getBundler()} watch for platform ${platform}.`, + ); + const bundlerProcess = this.bundlerProcesses[platform]; + await this.$cleanupService.removeKillProcess(bundlerProcess.pid.toString()); + if (bundlerProcess) { + bundlerProcess.kill("SIGINT"); + delete this.bundlerProcesses[platform]; } } private handleHMRMessage( - message: IWebpackMessage, + message: IBundlerMessage, platformData: IPlatformData, projectData: IProjectData, prepareData: IPrepareData, ) { - // handle new webpack hmr packets - this.$logger.trace("Received message from webpack process:", message); + // handle new bundler hmr packets + this.$logger.trace( + `Received message from ${projectData.bundler} process:`, + message, + ); if (message.type !== "compilation") { return; } - this.$logger.trace("Webpack build done!"); + this.$logger.trace( + `${capitalizeFirstLetter(projectData.bundler)} build done!`, + ); const files = message.data.emittedAssets.map((asset: string) => path.join( @@ -624,7 +682,7 @@ export class WebpackCompilerService return; } - this.emit(WEBPACK_COMPILATION_COMPLETE, { + this.emit(BUNDLER_COMPILATION_COMPLETE, { files, staleFiles, hasOnlyHotUpdateFiles: prepareData.hmr, @@ -636,9 +694,19 @@ export class WebpackCompilerService }); } - private getWebpackExecutablePath(projectData: IProjectData): string { - if (this.isWebpack5(projectData)) { - const packagePath = resolvePackagePath("@nativescript/webpack", { + private getBundlerExecutablePath(projectData: IProjectData): string { + const bundler = this.getBundler(); + + if (bundler === "vite") { + const packagePath = resolvePackagePath(`vite`, { + paths: [projectData.projectDir], + }); + + if (packagePath) { + return path.resolve(packagePath, "bin", "vite.js"); + } + } else if (this.isModernBundler(projectData)) { + const packagePath = resolvePackagePath(`@nativescript/${bundler}`, { paths: [projectData.projectDir], }); @@ -658,22 +726,37 @@ export class WebpackCompilerService return path.resolve(packagePath, "bin", "webpack.js"); } - private isWebpack5(projectData: IProjectData): boolean { - const packageJSONPath = resolvePackageJSONPath("@nativescript/webpack", { - paths: [projectData.projectDir], - }); + private isModernBundler(projectData: IProjectData): boolean { + const bundler = this.getBundler(); + switch (bundler) { + case "rspack": + return true; + default: + const packageJSONPath = resolvePackageJSONPath(WEBPACK_PLUGIN_NAME, { + paths: [projectData.projectDir], + }); - if (packageJSONPath) { - const packageData = this.$fs.readJson(packageJSONPath); - const ver = semver.coerce(packageData.version); + if (packageJSONPath) { + const packageData = this.$fs.readJson(packageJSONPath); + const ver = semver.coerce(packageData.version); - if (semver.satisfies(ver, ">= 5.0.0")) { - return true; - } + if (semver.satisfies(ver, ">= 5.0.0")) { + return true; + } + } + break; } return false; } + + public getBundler(): BundlerType { + return this.$projectConfigService.getValue(`bundler`, "webpack"); + } +} + +function capitalizeFirstLetter(val: string) { + return String(val).charAt(0).toUpperCase() + String(val).slice(1); } -injector.register("webpackCompilerService", WebpackCompilerService); +injector.register("bundlerCompilerService", BundlerCompilerService); diff --git a/lib/services/webpack/webpack.d.ts b/lib/services/bundler/bundler.ts similarity index 90% rename from lib/services/webpack/webpack.d.ts rename to lib/services/bundler/bundler.ts index e3a8dddd8b..691d45f8c5 100644 --- a/lib/services/webpack/webpack.d.ts +++ b/lib/services/bundler/bundler.ts @@ -18,21 +18,21 @@ import { import { INotConfiguredEnvOptions } from "../../common/definitions/commands"; declare global { - interface IWebpackCompilerService extends EventEmitter { + interface IBundlerCompilerService extends EventEmitter { compileWithWatch( platformData: IPlatformData, projectData: IProjectData, - prepareData: IPrepareData + prepareData: IPrepareData, ): Promise; compileWithoutWatch( platformData: IPlatformData, projectData: IProjectData, - prepareData: IPrepareData + prepareData: IPrepareData, ): Promise; - stopWebpackCompiler(platform: string): Promise; + stopBundlerCompiler(platform: string): Promise; } - interface IWebpackEnvOptions { + interface IBundlerEnvOptions { sourceMap?: boolean; uglify?: boolean; production?: boolean; @@ -42,19 +42,19 @@ declare global { checkForChanges( platformData: IPlatformData, projectData: IProjectData, - prepareData: IPrepareData + prepareData: IPrepareData, ): Promise; getPrepareInfoFilePath(platformData: IPlatformData): string; getPrepareInfo(platformData: IPlatformData): IPrepareInfo; savePrepareInfo( platformData: IPlatformData, projectData: IProjectData, - prepareData: IPrepareData + prepareData: IPrepareData, ): Promise; setNativePlatformStatus( platformData: IPlatformData, projectData: IProjectData, - addedPlatform: IAddedNativePlatform + addedPlatform: IAddedNativePlatform, ): void; currentChanges: IProjectChangesInfo; } @@ -68,7 +68,7 @@ declare global { hasNativeChanges: boolean; } - interface IWebpackEmitMessage { + interface IBundlerEmitMessage { emittedFiles: string[]; chunkFiles: string[]; hash: string; @@ -81,12 +81,12 @@ declare global { validate( projectData: IProjectData, options: IOptions, - notConfiguredEnvOptions?: INotConfiguredEnvOptions + notConfiguredEnvOptions?: INotConfiguredEnvOptions, ): Promise; createProject( frameworkDir: string, frameworkVersion: string, - projectData: IProjectData + projectData: IProjectData, ): Promise; interpolateData(projectData: IProjectData): Promise; interpolateConfigurationFile(projectData: IProjectData): void; @@ -108,13 +108,13 @@ declare global { validateOptions( projectId?: string, provision?: true | string, - teamId?: true | string + teamId?: true | string, ): Promise; buildProject( projectRoot: string, projectData: IProjectData, - buildConfig: T + buildConfig: T, ): Promise; /** @@ -125,7 +125,7 @@ declare global { */ prepareProject( projectData: IProjectData, - prepareData: T + prepareData: T, ): Promise; /** @@ -146,7 +146,7 @@ declare global { preparePluginNativeCode( pluginData: IPluginData, - options?: any + options?: any, ): Promise; /** @@ -157,17 +157,17 @@ declare global { */ removePluginNativeCode( pluginData: IPluginData, - projectData: IProjectData + projectData: IProjectData, ): Promise; beforePrepareAllPlugins( projectData: IProjectData, - dependencies?: IDependencyData[] + dependencies?: IDependencyData[], ): Promise; handleNativeDependenciesChange( projectData: IProjectData, - opts: IRelease + opts: IRelease, ): Promise; /** @@ -178,11 +178,11 @@ declare global { cleanDeviceTempFolder( deviceIdentifier: string, - projectData: IProjectData + projectData: IProjectData, ): Promise; processConfigurationFilesFromAppResources( projectData: IProjectData, - opts: { release: boolean } + opts: { release: boolean }, ): Promise; /** @@ -214,7 +214,7 @@ declare global { checkForChanges( changeset: IProjectChangesInfo, prepareData: T, - projectData: IProjectData + projectData: IProjectData, ): Promise; /** diff --git a/package-lock.json b/package-lock.json index 0a968451af..ba6411b0d3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "nativescript", - "version": "8.9.4", + "version": "9.0.0-alpha.4", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "nativescript", - "version": "8.9.4", + "version": "9.0.0-alpha.4", "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { @@ -119,7 +119,6 @@ "grunt-ts": "6.0.0-beta.22", "husky": "9.1.7", "istanbul": "0.4.5", - "latest-version": "9.0.0", "lint-staged": "~15.4.3", "mocha": "11.1.0", "sinon": "19.0.2", @@ -1379,51 +1378,6 @@ "node": ">=14" } }, - "node_modules/@pnpm/config.env-replace": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@pnpm/config.env-replace/-/config.env-replace-1.1.0.tgz", - "integrity": "sha512-htyl8TWnKL7K/ESFa1oW2UB5lVDxuF5DpM7tBi6Hu2LNL3mWkIzNLG6N4zoCUP1lCKNxWy/3iu8mS8MvToGd6w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12.22.0" - } - }, - "node_modules/@pnpm/network.ca-file": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@pnpm/network.ca-file/-/network.ca-file-1.0.2.tgz", - "integrity": "sha512-YcPQ8a0jwYU9bTdJDpXjMi7Brhkr1mXsXrUJvjqM2mQDgkRiz8jFaQGOdaLxgjtUfQgZhKy/O3cG/YwmgKaxLA==", - "dev": true, - "license": "MIT", - "dependencies": { - "graceful-fs": "4.2.10" - }, - "engines": { - "node": ">=12.22.0" - } - }, - "node_modules/@pnpm/network.ca-file/node_modules/graceful-fs": { - "version": "4.2.10", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", - "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", - "dev": true, - "license": "ISC" - }, - "node_modules/@pnpm/npm-conf": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/@pnpm/npm-conf/-/npm-conf-2.3.1.tgz", - "integrity": "sha512-c83qWb22rNRuB0UaVCI0uRPNRr8Z0FWnEIvT47jiHAmOIUHbBOg5XvV7pM5x+rKn9HRpjxquDbXYSXr3fAKFcw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@pnpm/config.env-replace": "^1.1.0", - "@pnpm/network.ca-file": "^1.0.1", - "config-chain": "^1.1.11" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/@prettier/plugin-xml": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/@prettier/plugin-xml/-/plugin-xml-2.2.0.tgz", @@ -3895,24 +3849,6 @@ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "license": "MIT" }, - "node_modules/config-chain": { - "version": "1.1.13", - "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz", - "integrity": "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ini": "^1.3.4", - "proto-list": "~1.2.1" - } - }, - "node_modules/config-chain/node_modules/ini": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", - "dev": true, - "license": "ISC" - }, "node_modules/continuable-cache": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/continuable-cache/-/continuable-cache-0.3.1.tgz", @@ -5240,16 +5176,6 @@ "node": ">=6" } }, - "node_modules/deep-extend": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4.0.0" - } - }, "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -9534,35 +9460,6 @@ "node": ">=6" } }, - "node_modules/ky": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/ky/-/ky-1.8.2.tgz", - "integrity": "sha512-XybQJ3d4Ea1kI27DoelE5ZCT3bSJlibYTtQuMsyzKox3TMyayw1asgQdl54WroAm+fIA3ZCr8zXW2RpR7qWVpA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sindresorhus/ky?sponsor=1" - } - }, - "node_modules/latest-version": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-9.0.0.tgz", - "integrity": "sha512-7W0vV3rqv5tokqkBAFV1LbR7HPOWzXQDpDgEuib/aJ1jsZZx6x3c2mBI+TJhJzOhkGeaLbCKEHXEXLfirtG2JA==", - "dev": true, - "license": "MIT", - "dependencies": { - "package-json": "^10.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/lazystream": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.1.tgz", @@ -11784,25 +11681,6 @@ "node": ">=6" } }, - "node_modules/package-json": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/package-json/-/package-json-10.0.1.tgz", - "integrity": "sha512-ua1L4OgXSBdsu1FPb7F3tYH0F48a6kxvod4pLUlGY9COeJAJQNX/sNH2IiEmsxw7lqYiAwrdHMjz1FctOsyDQg==", - "dev": true, - "license": "MIT", - "dependencies": { - "ky": "^1.2.0", - "registry-auth-token": "^5.0.2", - "registry-url": "^6.0.1", - "semver": "^7.6.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/package-json-from-dist": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", @@ -12500,13 +12378,6 @@ "signal-exit": "^3.0.2" } }, - "node_modules/proto-list": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", - "integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==", - "dev": true, - "license": "ISC" - }, "node_modules/proxy-from-env": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", @@ -12639,39 +12510,6 @@ "dev": true, "license": "MIT" }, - "node_modules/rc": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", - "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", - "dev": true, - "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", - "dependencies": { - "deep-extend": "^0.6.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" - }, - "bin": { - "rc": "cli.js" - } - }, - "node_modules/rc/node_modules/ini": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", - "dev": true, - "license": "ISC" - }, - "node_modules/rc/node_modules/strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/read-cmd-shim": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/read-cmd-shim/-/read-cmd-shim-5.0.0.tgz", @@ -13089,35 +12927,6 @@ "integrity": "sha512-tlbJqcMHnPKI9zSrystikWKwHkBqu2a/Sgw01h3zFjvYrMxEDYHzzoMZnUrbIfpTFEsoRnnviOXNCzFiSc54Qw==", "license": "MIT" }, - "node_modules/registry-auth-token": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-5.1.0.tgz", - "integrity": "sha512-GdekYuwLXLxMuFTwAPg5UKGLW/UXzQrZvH/Zj791BQif5T05T0RsaLfHc9q3ZOKi7n+BoprPD9mJ0O0k4xzUlw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@pnpm/npm-conf": "^2.1.0" - }, - "engines": { - "node": ">=14" - } - }, - "node_modules/registry-url": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-6.0.1.tgz", - "integrity": "sha512-+crtS5QjFRqFCoQmvGduwYWEBng99ZvmFvF+cUJkGYF1L1BfU8C6Zp9T7f5vPAwyLkUExpvK+ANVZmGU49qi4Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "rc": "1.2.8" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/remove-trailing-separator": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", diff --git a/package.json b/package.json index 60976bca6d..254c15d3ea 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "nativescript", "main": "./lib/nativescript-cli-lib.js", - "version": "8.9.4", + "version": "9.0.0-alpha.4", "author": "NativeScript ", "description": "Command-line interface for building NativeScript projects", "bin": { @@ -157,7 +157,6 @@ "grunt-ts": "6.0.0-beta.22", "husky": "9.1.7", "istanbul": "0.4.5", - "latest-version": "9.0.0", "lint-staged": "~15.4.3", "mocha": "11.1.0", "sinon": "19.0.2", diff --git a/packages/doctor/src/android-tools-info.ts b/packages/doctor/src/android-tools-info.ts index 7c5dfb67e4..90b7ad5989 100644 --- a/packages/doctor/src/android-tools-info.ts +++ b/packages/doctor/src/android-tools-info.ts @@ -71,11 +71,11 @@ export class AndroidToolsInfo implements NativeScriptDoctor.IAndroidToolsInfo { private childProcess: ChildProcess, private fs: FileSystem, private hostInfo: HostInfo, - private helpers: Helpers + private helpers: Helpers, ) {} public getToolsInfo( - config: Partial = {} + config: Partial = {}, ): NativeScriptDoctor.IAndroidToolsInfoData { if (!this.toolsInfo) { const infoData: NativeScriptDoctor.IAndroidToolsInfoData = @@ -84,7 +84,7 @@ export class AndroidToolsInfo implements NativeScriptDoctor.IAndroidToolsInfo { infoData.installedTargets = this.getInstalledTargets(); infoData.compileSdkVersion = this.getCompileSdk( infoData.installedTargets, - config.projectDir + config.projectDir, ); infoData.buildToolsVersion = this.getBuildToolsVersion(config.projectDir); @@ -95,14 +95,14 @@ export class AndroidToolsInfo implements NativeScriptDoctor.IAndroidToolsInfo { } public validateInfo( - config: Partial = {} + config: Partial = {}, ): NativeScriptDoctor.IWarning[] { const errors: NativeScriptDoctor.IWarning[] = []; const toolsInfoData = this.getToolsInfo(config); const isAndroidHomeValid = this.isAndroidHomeValid(); if (!toolsInfoData.compileSdkVersion) { const supportedTargetsForAndroidRuntime = this.getSupportedTargets( - config.projectDir + config.projectDir, ); errors.push({ warning: [ @@ -110,7 +110,7 @@ export class AndroidToolsInfo implements NativeScriptDoctor.IAndroidToolsInfo { `To be able to build for Android with your current android runtime, install one of the following supported Android SDK targets:`, ...supportedTargetsForAndroidRuntime.map((target) => ` ${target}`), `Supported targets vary based on what android runtime you have installed. Currently your app uses @nativescript/android ${this.getRuntimeVersion( - { projectDir: config.projectDir } + { projectDir: config.projectDir }, )}`, ].join("\n"), additionalInformation: `Run \`\$ ${this.getPathToSdkManagementTool()}\` to manage your Android SDK versions.`, @@ -121,7 +121,7 @@ export class AndroidToolsInfo implements NativeScriptDoctor.IAndroidToolsInfo { if (!toolsInfoData.buildToolsVersion) { const buildToolsRange = this.getBuildToolsRange(config.projectDir); const versionRangeMatches = buildToolsRange.match( - /^.*?([\d\.]+)\s+.*?([\d\.]+)$/ + /^.*?([\d\.]+)\s+.*?([\d\.]+)$/, ); let message = `You can install any version in the following range: '${buildToolsRange}'.`; @@ -155,7 +155,7 @@ export class AndroidToolsInfo implements NativeScriptDoctor.IAndroidToolsInfo { } public static unsupportedJavaMessage( - installedJavaCompilerVersion: string + installedJavaCompilerVersion: string, ): string { return `Javac version ${installedJavaCompilerVersion} is not supported. You must install a java version greater than ${ AndroidToolsInfo.MIN_JAVA_VERSION @@ -169,7 +169,7 @@ export class AndroidToolsInfo implements NativeScriptDoctor.IAndroidToolsInfo { public validateJavacVersion( installedJavaCompilerVersion: string, projectDir?: string, - runtimeVersion?: string + runtimeVersion?: string, ): NativeScriptDoctor.IWarning[] { const errors: NativeScriptDoctor.IWarning[] = []; @@ -196,17 +196,17 @@ export class AndroidToolsInfo implements NativeScriptDoctor.IAndroidToolsInfo { if ( semver.lt( installedJavaCompilerSemverVersion, - AndroidToolsInfo.MIN_JAVA_VERSION + AndroidToolsInfo.MIN_JAVA_VERSION, ) || (AndroidToolsInfo.MAX_JAVA_VERSION ? semver.gte( installedJavaCompilerSemverVersion, - AndroidToolsInfo.MAX_JAVA_VERSION - ) + AndroidToolsInfo.MAX_JAVA_VERSION, + ) : false) ) { warning = AndroidToolsInfo.unsupportedJavaMessage( - installedJavaCompilerVersion + installedJavaCompilerVersion, ); } else { runtimeVersion = this.getRuntimeVersion({ runtimeVersion, projectDir }); @@ -288,7 +288,7 @@ export class AndroidToolsInfo implements NativeScriptDoctor.IAndroidToolsInfo { }); } else if ( expectedDirectoriesInAndroidHome.map((dir) => - this.fs.exists(path.join(this.androidHome, dir)) + this.fs.exists(path.join(this.androidHome, dir)), ).length === 0 ) { errors.push({ @@ -316,7 +316,7 @@ export class AndroidToolsInfo implements NativeScriptDoctor.IAndroidToolsInfo { if (!_.includes(supportedTargets, newTarget)) { const supportedVersions = supportedTargets.sort(); const minSupportedVersion = this.parseAndroidSdkString( - _.first(supportedVersions) + _.first(supportedVersions), ); if (!targetSupported && targetSdk && targetSdk < minSupportedVersion) { @@ -339,7 +339,7 @@ export class AndroidToolsInfo implements NativeScriptDoctor.IAndroidToolsInfo { const newTarget = `${this.ANDROID_TARGET_PREFIX}-${targetSdk}`; const targetSupported = _.includes( this.getSupportedTargets(projectDir), - newTarget + newTarget, ); if ( @@ -369,7 +369,7 @@ export class AndroidToolsInfo implements NativeScriptDoctor.IAndroidToolsInfo { const pathToEmulatorFromAndroidStudio = path.join( this.androidHome, emulatorExecutableName, - emulatorExecutableName + emulatorExecutableName, ); const realFilePath = this.hostInfo.isWindows ? `${pathToEmulatorFromAndroidStudio}.exe` @@ -381,7 +381,7 @@ export class AndroidToolsInfo implements NativeScriptDoctor.IAndroidToolsInfo { this.pathToEmulatorExecutable = path.join( this.androidHome, "tools", - emulatorExecutableName + emulatorExecutableName, ); } } @@ -403,12 +403,12 @@ export class AndroidToolsInfo implements NativeScriptDoctor.IAndroidToolsInfo { this.androidHome, "tools", "bin", - sdkmanagerName + sdkmanagerName, ); const pathToAndroidExecutable = path.join( this.androidHome, "tools", - "android" + "android", ); const pathToExecutable = this.fs.exists(pathToSdkmanager) ? pathToSdkmanager @@ -416,7 +416,7 @@ export class AndroidToolsInfo implements NativeScriptDoctor.IAndroidToolsInfo { sdkManagementToolPath = pathToExecutable.replace( this.androidHome, - this.hostInfo.isWindows ? "%ANDROID_HOME%" : "$ANDROID_HOME" + this.hostInfo.isWindows ? "%ANDROID_HOME%" : "$ANDROID_HOME", ); } @@ -425,15 +425,15 @@ export class AndroidToolsInfo implements NativeScriptDoctor.IAndroidToolsInfo { private getCompileSdk( installedTargets: string[], - projectDir: string + projectDir: string, ): number { const latestValidAndroidTarget = this.getLatestValidAndroidTarget( installedTargets, - projectDir + projectDir, ); if (latestValidAndroidTarget) { const integerVersion = this.parseAndroidSdkString( - latestValidAndroidTarget + latestValidAndroidTarget, ); if ( @@ -483,7 +483,7 @@ export class AndroidToolsInfo implements NativeScriptDoctor.IAndroidToolsInfo { const buildToolsRange = this.getBuildToolsRange(projectDir); buildToolsVersion = this.getMatchingDir( pathToBuildTools, - buildToolsRange + buildToolsRange, ); } @@ -492,17 +492,17 @@ export class AndroidToolsInfo implements NativeScriptDoctor.IAndroidToolsInfo { private getLatestValidAndroidTarget( installedTargets: string[], - projectDir: string + projectDir: string, ): string { return _.findLast( this.getSupportedTargets(projectDir).sort(), - (supportedTarget) => _.includes(installedTargets, supportedTarget) + (supportedTarget) => _.includes(installedTargets, supportedTarget), ); } private parseAndroidSdkString(androidSdkString: string): number { return parseInt( - androidSdkString.replace(`${this.ANDROID_TARGET_PREFIX}-`, "") + androidSdkString.replace(`${this.ANDROID_TARGET_PREFIX}-`, ""), ); } @@ -523,7 +523,7 @@ export class AndroidToolsInfo implements NativeScriptDoctor.IAndroidToolsInfo { const supportedTargets = this.getSupportedTargets(projectDir); return this.parseAndroidSdkString( - supportedTargets.sort()[supportedTargets.length - 1] + supportedTargets.sort()[supportedTargets.length - 1], ); } @@ -581,7 +581,7 @@ export class AndroidToolsInfo implements NativeScriptDoctor.IAndroidToolsInfo { depName === Constants.ANDROID_SCOPED_RUNTIME || depName === Constants.ANDROID_OLD_RUNTIME ); - } + }, ); if (foundRuntime) { @@ -593,7 +593,7 @@ export class AndroidToolsInfo implements NativeScriptDoctor.IAndroidToolsInfo { `${foundRuntime}/package.json`, { paths: [projectDir], - } + }, ); version = require(packagePath).version; } catch (err) { @@ -636,7 +636,7 @@ export class AndroidToolsInfo implements NativeScriptDoctor.IAndroidToolsInfo { if (!semver.validRange(runtimeVersion)) { try { const npmViewOutput = this.childProcess.execSync( - `npm view ${runtimePackage.name} dist-tags --json` + `npm view ${runtimePackage.name} dist-tags --json`, ); const jsonNpmViewOutput = JSON.parse(npmViewOutput); @@ -650,7 +650,7 @@ export class AndroidToolsInfo implements NativeScriptDoctor.IAndroidToolsInfo { if (runtimeVersion && !semver.validRange(runtimeVersion)) { // If we got here, something terribly wrong happened. throw new Error( - `The determined Android runtime version ${runtimeVersion} is not valid. Unable to verify if the current system is setup for Android development.` + `The determined Android runtime version ${runtimeVersion} is not valid. Unable to verify if the current system is setup for Android development.`, ); } @@ -661,7 +661,7 @@ export class AndroidToolsInfo implements NativeScriptDoctor.IAndroidToolsInfo { private getMaxSupportedCompileVersion( config: Partial & { runtimeVersion?: string; - } + }, ): number { if ( config.runtimeVersion && @@ -670,7 +670,7 @@ export class AndroidToolsInfo implements NativeScriptDoctor.IAndroidToolsInfo { return 28; } return this.parseAndroidSdkString( - _.last(this.getSupportedTargets(config.projectDir).sort()) + _.last(this.getSupportedTargets(config.projectDir).sort()), ); } } diff --git a/packages/doctor/test/android-tools-info.ts b/packages/doctor/test/android-tools-info.ts index 90a24bb552..89d1eb3f31 100644 --- a/packages/doctor/test/android-tools-info.ts +++ b/packages/doctor/test/android-tools-info.ts @@ -42,7 +42,7 @@ describe("androidToolsInfo", () => { devDependencies: { "@nativescript/android": runtimeVersion, }, - } + } : null; }, readDirectory: (path: string) => { @@ -114,7 +114,7 @@ describe("androidToolsInfo", () => { const assertSupportedRange = ( runtimeVersion: string, min: number, - max: number + max: number, ) => { let cnt = 0; const androidToolsInfo = getAndroidToolsInfo(runtimeVersion); @@ -211,7 +211,7 @@ describe("androidToolsInfo", () => { const androidToolsInfo = getAndroidToolsInfo(runtimeVersion); const actualWarnings = androidToolsInfo.validateJavacVersion( javacVersion, - "/Users/username/projectDir" + "/Users/username/projectDir", ); let expectedWarnings: NativeScriptDoctor.IWarning[] = []; @@ -228,7 +228,7 @@ describe("androidToolsInfo", () => { assert.deepEqual(actualWarnings, expectedWarnings); }); - } + }, ); const npmTagsTestData: ITestData[] = [ @@ -323,12 +323,12 @@ describe("androidToolsInfo", () => { childProcess, fs, hostInfo, - helpers + helpers, ); const actualWarnings = androidToolsInfo.validateJavacVersion( javacVersion, - "/Users/username/projectDir" + "/Users/username/projectDir", ); let expectedWarnings: NativeScriptDoctor.IWarning[] = []; if (warnings && warnings.length) { @@ -345,10 +345,10 @@ describe("androidToolsInfo", () => { assert.deepEqual(actualWarnings, expectedWarnings); assert.equal( execSyncCommand, - "npm view tns-android dist-tags --json" + "npm view tns-android dist-tags --json", ); }); - } + }, ); }); diff --git a/test/controllers/prepare-controller.ts b/test/controllers/prepare-controller.ts index a8bf78ad3b..e3982de1e4 100644 --- a/test/controllers/prepare-controller.ts +++ b/test/controllers/prepare-controller.ts @@ -38,7 +38,7 @@ function createTestInjector(data: { hasNativeChanges: boolean }): IInjector { }, }); - injector.register("webpackCompilerService", { + injector.register("bundlerCompilerService", { on: () => ({}), emit: () => ({}), compileWithWatch: async () => { @@ -119,7 +119,7 @@ describe("prepareController", () => { injector.resolve("prepareController"); const prepareNativePlatformService = injector.resolve( - "prepareNativePlatformService" + "prepareNativePlatformService", ); prepareNativePlatformService.prepareNativePlatform = async () => { const nativeFilesWatcher = (prepareController).watchersData[ @@ -128,7 +128,7 @@ describe("prepareController", () => { nativeFilesWatcher.emit( "all", "change", - "my/project/App_Resources/some/file" + "my/project/App_Resources/some/file", ); isNativePrepareCalled = true; return false; diff --git a/test/project-data.ts b/test/project-data.ts index 7ce33a1320..5b8c747bd1 100644 --- a/test/project-data.ts +++ b/test/project-data.ts @@ -56,14 +56,16 @@ describe("projectData", () => { configData?: { shared?: boolean; webpackConfigPath?: string; + bundlerConfigPath?: string; projectName?: string; + bundler?: string; }; }): IProjectData => { const testInjector = createTestInjector(); const fs = testInjector.resolve("fs"); testInjector.register( "projectConfigService", - stubs.ProjectConfigServiceStub.initWithConfig(opts?.configData) + stubs.ProjectConfigServiceStub.initWithConfig(opts?.configData), ); fs.exists = (filePath: string) => { @@ -98,7 +100,7 @@ describe("projectData", () => { const assertProjectType = ( dependencies: any, devDependencies: any, - expectedProjecType: string + expectedProjecType: string, ) => { const projectData = prepareTest({ packageJsonData: { @@ -125,7 +127,7 @@ describe("projectData", () => { assertProjectType( { "nativescript-vue": "*" }, { typescript: "*" }, - "Vue.js" + "Vue.js", ); }); @@ -141,7 +143,7 @@ describe("projectData", () => { assertProjectType( null, { "nativescript-dev-typescript": "*" }, - "Pure TypeScript" + "Pure TypeScript", ); }); @@ -195,13 +197,13 @@ describe("projectData", () => { const projectData = prepareTest(); assert.equal( projectData.webpackConfigPath, - path.join(projectDir, "webpack.config.js") + path.join(projectDir, "webpack.config.js"), ); }); it("returns correct path when full path is set in nsconfig.json", () => { const pathToConfig = path.resolve( - path.join("/testDir", "innerDir", "mywebpack.config.js") + path.join("/testDir", "innerDir", "mywebpack.config.js"), ); const projectData = prepareTest({ configData: { webpackConfigPath: pathToConfig }, @@ -211,7 +213,7 @@ describe("projectData", () => { it("returns correct path when relative path is set in nsconfig.json", () => { const pathToConfig = path.resolve( - path.join("projectDir", "innerDir", "mywebpack.config.js") + path.join("projectDir", "innerDir", "mywebpack.config.js"), ); const projectData = prepareTest({ configData: { @@ -221,4 +223,81 @@ describe("projectData", () => { assert.equal(projectData.webpackConfigPath, pathToConfig); }); }); + + describe("bundlerConfigPath", () => { + it("default path to webpack.config.js is set when nsconfig.json does not set value", () => { + const projectData = prepareTest(); + assert.equal( + projectData.bundlerConfigPath, + path.join(projectDir, "webpack.config.js"), + ); + }); + + it("should use webpackConfigPath property when bundlerConfigPath is not defined", () => { + const pathToConfig = path.resolve( + path.join("/testDir", "innerDir", "mywebpack.config.js"), + ); + const projectData = prepareTest({ + configData: { webpackConfigPath: pathToConfig }, + }); + assert.equal(projectData.bundlerConfigPath, pathToConfig); + }); + + it("returns correct path when full path is set in nsconfig.json", () => { + const pathToConfig = path.resolve( + path.join("/testDir", "innerDir", "mywebpack.config.js"), + ); + const projectData = prepareTest({ + configData: { bundlerConfigPath: pathToConfig }, + }); + assert.equal(projectData.bundlerConfigPath, pathToConfig); + }); + + it("returns correct path when relative path is set in nsconfig.json", () => { + const pathToConfig = path.resolve( + path.join("projectDir", "innerDir", "mywebpack.config.js"), + ); + const projectData = prepareTest({ + configData: { + bundlerConfigPath: path.join("./innerDir", "mywebpack.config.js"), + }, + }); + assert.equal(projectData.bundlerConfigPath, pathToConfig); + }); + + it("should use bundlerConfigPath instead of webpackConfigPath if both are defined.", () => { + const pathToConfig = path.resolve( + path.join("projectDir", "innerDir", "myrspack.config.js"), + ); + const projectData = prepareTest({ + configData: { + webpackConfigPath: path.join("./innerDir", "mywebpack.config.js"), + bundlerConfigPath: path.join("./innerDir", "myrspack.config.js"), + }, + }); + assert.equal(projectData.bundlerConfigPath, pathToConfig); + }); + }); + + describe("bundler", () => { + it("sets bundler to 'webpack' by default when nsconfig.json does not specify a bundler", () => { + const projectData = prepareTest(); + assert.equal(projectData.bundler, "webpack"); + }); + + it("sets bundler to 'webpack' when explicitly defined in nsconfig.json", () => { + const projectData = prepareTest({ configData: { bundler: "webpack" } }); + assert.equal(projectData.bundler, "webpack"); + }); + + it("sets bundler to 'rspack' when explicitly defined in nsconfig.json", () => { + const projectData = prepareTest({ configData: { bundler: "rspack" } }); + assert.equal(projectData.bundler, "rspack"); + }); + + it("sets bundler to 'vite' when explicitly defined in nsconfig.json", () => { + const projectData = prepareTest({ configData: { bundler: "vite" } }); + assert.equal(projectData.bundler, "vite"); + }); + }); }); diff --git a/test/services/webpack/webpack-compiler-service.ts b/test/services/bundler/bundler-compiler-service.ts similarity index 62% rename from test/services/webpack/webpack-compiler-service.ts rename to test/services/bundler/bundler-compiler-service.ts index d15cccc430..49d69a55f4 100644 --- a/test/services/webpack/webpack-compiler-service.ts +++ b/test/services/bundler/bundler-compiler-service.ts @@ -1,5 +1,5 @@ import { Yok } from "../../../lib/common/yok"; -import { WebpackCompilerService } from "../../../lib/services/webpack/webpack-compiler-service"; +import { BundlerCompilerService } from "../../../lib/services/bundler/bundler-compiler-service"; import { assert } from "chai"; import { ErrorsStub } from "../../stubs"; import { IInjector } from "../../../lib/common/definitions/yok"; @@ -23,7 +23,7 @@ function createTestInjector(): IInjector { testInjector.register("packageManager", { getPackageManagerName: async () => "npm", }); - testInjector.register("webpackCompilerService", WebpackCompilerService); + testInjector.register("bundlerCompilerService", BundlerCompilerService); testInjector.register("childProcess", {}); testInjector.register("hooksService", {}); testInjector.register("hostInfo", {}); @@ -33,6 +33,9 @@ function createTestInjector(): IInjector { testInjector.register("packageInstallationManager", {}); testInjector.register("mobileHelper", {}); testInjector.register("cleanupService", {}); + testInjector.register("projectConfigService", { + getValue: (key: string, defaultValue?: string) => defaultValue, + }); testInjector.register("fs", { exists: (filePath: string) => true, }); @@ -40,23 +43,23 @@ function createTestInjector(): IInjector { return testInjector; } -describe("WebpackCompilerService", () => { +describe("BundlerCompilerService", () => { let testInjector: IInjector = null; - let webpackCompilerService: WebpackCompilerService = null; + let bundlerCompilerService: BundlerCompilerService = null; beforeEach(() => { testInjector = createTestInjector(); - webpackCompilerService = testInjector.resolve(WebpackCompilerService); + bundlerCompilerService = testInjector.resolve(BundlerCompilerService); }); describe("getUpdatedEmittedFiles", () => { // backwards compatibility with old versions of nativescript-dev-webpack it("should return only hot updates when nextHash is not provided", async () => { - const result = webpackCompilerService.getUpdatedEmittedFiles( + const result = bundlerCompilerService.getUpdatedEmittedFiles( getAllEmittedFiles("hash1"), chunkFiles, null, - iOSPlatformName + iOSPlatformName, ); const expectedEmittedFiles = [ "bundle.hash1.hot-update.js", @@ -65,19 +68,19 @@ describe("WebpackCompilerService", () => { assert.deepStrictEqual(result.emittedFiles, expectedEmittedFiles); }); - // 2 successful webpack compilations + // 2 successful bundler compilations it("should return only hot updates when nextHash is provided", async () => { - webpackCompilerService.getUpdatedEmittedFiles( + bundlerCompilerService.getUpdatedEmittedFiles( getAllEmittedFiles("hash1"), chunkFiles, "hash2", - iOSPlatformName + iOSPlatformName, ); - const result = webpackCompilerService.getUpdatedEmittedFiles( + const result = bundlerCompilerService.getUpdatedEmittedFiles( getAllEmittedFiles("hash2"), chunkFiles, "hash3", - iOSPlatformName + iOSPlatformName, ); assert.deepStrictEqual(result.emittedFiles, [ @@ -85,19 +88,19 @@ describe("WebpackCompilerService", () => { "hash2.hot-update.json", ]); }); - // 1 successful webpack compilation, n compilations with no emitted files - it("should return all files when there is a webpack compilation with no emitted files", () => { - webpackCompilerService.getUpdatedEmittedFiles( + // 1 successful bundler compilation, n compilations with no emitted files + it("should return all files when there is a bundler compilation with no emitted files", () => { + bundlerCompilerService.getUpdatedEmittedFiles( getAllEmittedFiles("hash1"), chunkFiles, "hash2", - iOSPlatformName + iOSPlatformName, ); - const result = webpackCompilerService.getUpdatedEmittedFiles( + const result = bundlerCompilerService.getUpdatedEmittedFiles( getAllEmittedFiles("hash4"), chunkFiles, "hash5", - iOSPlatformName + iOSPlatformName, ); assert.deepStrictEqual(result.emittedFiles, [ @@ -107,25 +110,25 @@ describe("WebpackCompilerService", () => { "hash4.hot-update.json", ]); }); - // 1 successful webpack compilation, n compilations with no emitted files, 1 successful webpack compilation + // 1 successful bundler compilation, n compilations with no emitted files, 1 successful bundler compilation it("should return only hot updates after fixing the compilation error", () => { - webpackCompilerService.getUpdatedEmittedFiles( + bundlerCompilerService.getUpdatedEmittedFiles( getAllEmittedFiles("hash1"), chunkFiles, "hash2", - iOSPlatformName + iOSPlatformName, ); - webpackCompilerService.getUpdatedEmittedFiles( + bundlerCompilerService.getUpdatedEmittedFiles( getAllEmittedFiles("hash5"), chunkFiles, "hash6", - iOSPlatformName + iOSPlatformName, ); - const result = webpackCompilerService.getUpdatedEmittedFiles( + const result = bundlerCompilerService.getUpdatedEmittedFiles( getAllEmittedFiles("hash6"), chunkFiles, "hash7", - iOSPlatformName + iOSPlatformName, ); assert.deepStrictEqual(result.emittedFiles, [ @@ -133,16 +136,16 @@ describe("WebpackCompilerService", () => { "hash6.hot-update.json", ]); }); - // 1 webpack compilation with no emitted files + // 1 bundler compilation with no emitted files it("should return all files when first compilation on livesync change is not successful", () => { - (webpackCompilerService).expectedHashes = { + (bundlerCompilerService).expectedHashes = { ios: "hash1", }; - const result = webpackCompilerService.getUpdatedEmittedFiles( + const result = bundlerCompilerService.getUpdatedEmittedFiles( getAllEmittedFiles("hash1"), chunkFiles, "hash2", - iOSPlatformName + iOSPlatformName, ); assert.deepStrictEqual(result.emittedFiles, [ @@ -151,48 +154,48 @@ describe("WebpackCompilerService", () => { ]); }); it("should return correct hashes when there are more than one platform", () => { - webpackCompilerService.getUpdatedEmittedFiles( + bundlerCompilerService.getUpdatedEmittedFiles( getAllEmittedFiles("hash1"), chunkFiles, "hash2", - iOSPlatformName + iOSPlatformName, ); - webpackCompilerService.getUpdatedEmittedFiles( + bundlerCompilerService.getUpdatedEmittedFiles( getAllEmittedFiles("hash3"), chunkFiles, "hash4", - androidPlatformName + androidPlatformName, ); - webpackCompilerService.getUpdatedEmittedFiles( + bundlerCompilerService.getUpdatedEmittedFiles( getAllEmittedFiles("hash2"), chunkFiles, "hash5", - iOSPlatformName + iOSPlatformName, ); - webpackCompilerService.getUpdatedEmittedFiles( + bundlerCompilerService.getUpdatedEmittedFiles( getAllEmittedFiles("hash4"), chunkFiles, "hash6", - androidPlatformName + androidPlatformName, ); - const iOSResult = webpackCompilerService.getUpdatedEmittedFiles( + const iOSResult = bundlerCompilerService.getUpdatedEmittedFiles( getAllEmittedFiles("hash5"), chunkFiles, "hash7", - iOSPlatformName + iOSPlatformName, ); assert.deepStrictEqual(iOSResult.emittedFiles, [ "bundle.hash5.hot-update.js", "hash5.hot-update.json", ]); - const androidResult = webpackCompilerService.getUpdatedEmittedFiles( + const androidResult = bundlerCompilerService.getUpdatedEmittedFiles( getAllEmittedFiles("hash6"), chunkFiles, "hash8", - androidPlatformName + androidPlatformName, ); assert.deepStrictEqual(androidResult.emittedFiles, [ "bundle.hash6.hot-update.js", @@ -202,33 +205,33 @@ describe("WebpackCompilerService", () => { }); describe("compileWithWatch", () => { - it("fails when the value set for webpackConfigPath is not existant file", async () => { - const webpackConfigPath = "some path.js"; + it("fails when the value set for bundlerConfigPath is not existant file", async () => { + const bundlerConfigPath = "some path.js"; testInjector.resolve("fs").exists = (filePath: string) => - filePath !== webpackConfigPath; + filePath !== bundlerConfigPath; await assert.isRejected( - webpackCompilerService.compileWithWatch( + bundlerCompilerService.compileWithWatch( { platformNameLowerCase: "android" }, - { webpackConfigPath }, - {} + { bundlerConfigPath: bundlerConfigPath }, + {}, ), - `The webpack configuration file ${webpackConfigPath} does not exist. Ensure the file exists, or update the path in ${CONFIG_FILE_NAME_DISPLAY}` + `The bundler configuration file ${bundlerConfigPath} does not exist. Ensure the file exists, or update the path in ${CONFIG_FILE_NAME_DISPLAY}`, ); }); }); describe("compileWithoutWatch", () => { - it("fails when the value set for webpackConfigPath is not existant file", async () => { - const webpackConfigPath = "some path.js"; + it("fails when the value set for bundlerConfigPath is not existant file", async () => { + const bundlerConfigPath = "some path.js"; testInjector.resolve("fs").exists = (filePath: string) => - filePath !== webpackConfigPath; + filePath !== bundlerConfigPath; await assert.isRejected( - webpackCompilerService.compileWithoutWatch( + bundlerCompilerService.compileWithoutWatch( { platformNameLowerCase: "android" }, - { webpackConfigPath }, - {} + { bundlerConfigPath: bundlerConfigPath }, + {}, ), - `The webpack configuration file ${webpackConfigPath} does not exist. Ensure the file exists, or update the path in ${CONFIG_FILE_NAME_DISPLAY}` + `The bundler configuration file ${bundlerConfigPath} does not exist. Ensure the file exists, or update the path in ${CONFIG_FILE_NAME_DISPLAY}`, ); }); }); diff --git a/test/stubs.ts b/test/stubs.ts index fc4d687c15..7ec3580891 100644 --- a/test/stubs.ts +++ b/test/stubs.ts @@ -38,6 +38,7 @@ import { IProjectConfigInformation, IProjectBackupService, IBackup, + BundlerType, } from "../lib/definitions/project"; import { IPlatformData, @@ -658,6 +659,8 @@ export class ProjectDataStub implements IProjectData { projectDir: string; projectName: string; webpackConfigPath: string; + bundlerConfigPath: string; + bundler: BundlerType; get platformsDir(): string { return (