diff --git a/lib/nativescript-cli.ts b/lib/nativescript-cli.ts index f9932fc1a3..04b2f90fd3 100644 --- a/lib/nativescript-cli.ts +++ b/lib/nativescript-cli.ts @@ -3,6 +3,35 @@ require("./bootstrap"); import * as shelljs from "shelljs"; shelljs.config.silent = true; shelljs.config.fatal = true; + +if (process.platform === "win32") { + // Later versions of shelljs do not process globs with \ path delimiters correctly, for windows change to / + const realcp = shelljs.cp; + (shelljs as any).cp = (...args: unknown[]) => { + if (args.length === 3) { + args[1] = replaceDashes(args[1] as string | string[]); + } else { + args[0] = replaceDashes(args[0] as string | string[]); + } + + if (args.length == 2) { + realcp(args[0] as string[], args[1] as string); + } else { + realcp(args[0] as string, args[1] as string[], args[2] as string); + } + }; + function replaceDashes(values: string | string[]): string | string[] { + if (Array.isArray(values)) { + for (let i = 0; i < values.length; ++i) { + values[i] = replaceDashes(values[i]) as string; + } + return values; + } else { + return values.replace(/\\/g, "/"); + } + } +} + import { installUncaughtExceptionListener } from "./common/errors"; import { settlePromises } from "./common/helpers"; import { injector } from "./common/yok"; @@ -14,7 +43,7 @@ import { import { IInitializeService } from "./definitions/initialize-service"; import { color } from "./color"; installUncaughtExceptionListener( - process.exit.bind(process, ErrorCodes.UNCAUGHT) + process.exit.bind(process, ErrorCodes.UNCAUGHT), ); const logger: ILogger = injector.resolve("logger"); @@ -23,7 +52,7 @@ export const originalProcessOn = process.on.bind(process); process.on = (event: string, listener: any): any => { if (event === "SIGINT") { logger.trace( - `Trying to handle SIGINT event. CLI overrides this behavior and does not allow handling SIGINT as this causes issues with Ctrl + C in terminal.` + `Trying to handle SIGINT event. CLI overrides this behavior and does not allow handling SIGINT as this causes issues with Ctrl + C in terminal.`, ); const msg = "The stackTrace of the location trying to handle SIGINT is"; const stackTrace = new Error(msg).stack || ""; @@ -31,9 +60,9 @@ process.on = (event: string, listener: any): any => { stackTrace.replace( `Error: ${msg}`, `${msg} (${color.yellow( - "note:" - )} this is not an error, just a stack-trace for debugging purposes):` - ) + "note:", + )} this is not an error, just a stack-trace for debugging purposes):`, + ), ); } else { return originalProcessOn(event, listener); @@ -52,13 +81,12 @@ process.on = (event: string, listener: any): any => { const err: IErrors = injector.resolve("$errors"); err.printCallStack = config.DEBUG; - const $initializeService = injector.resolve( - "initializeService" - ); + const $initializeService = + injector.resolve("initializeService"); await $initializeService.initialize(); const extensibilityService: IExtensibilityService = injector.resolve( - "extensibilityService" + "extensibilityService", ); try { await settlePromises(extensibilityService.loadExtensions()); @@ -66,9 +94,8 @@ process.on = (event: string, listener: any): any => { logger.trace("Unable to load extensions. Error is: ", err); } - const commandDispatcher: ICommandDispatcher = injector.resolve( - "commandDispatcher" - ); + const commandDispatcher: ICommandDispatcher = + injector.resolve("commandDispatcher"); // unused... // const messages: IMessagesService = injector.resolve("$messagesService"); diff --git a/lib/services/bundler/bundler-compiler-service.ts b/lib/services/bundler/bundler-compiler-service.ts index a888600bcf..d8c3403db7 100644 --- a/lib/services/bundler/bundler-compiler-service.ts +++ b/lib/services/bundler/bundler-compiler-service.ts @@ -2,6 +2,8 @@ import * as path from "path"; import * as child_process from "child_process"; import * as semver from "semver"; import * as _ from "lodash"; +// TODO: can switch to file-system service +import { mkdirSync, readdirSync, existsSync, copyFileSync, rmSync } from "fs"; import { EventEmitter } from "events"; import { performanceLog } from "../../common/decorators"; import { @@ -126,78 +128,37 @@ export class BundlerCompilerService } // Copy Vite output files directly to platform destination - const distOutput = path.join(projectData.projectDir, "dist"); + const distOutput = path.join( + projectData.projectDir, + ".ns-vite-build", + ); const destDir = path.join( platformData.appDestinationDirectoryPath, this.$options.hostProjectModuleName, ); if (debugLog) { - console.log(`🔥 Copying from ${distOutput} to ${destDir}`); + console.log(`Copying from ${distOutput} to ${destDir}.`); } // Determine which files to copy based on build type and changes - if (message.isHMR) { - // HMR updates: only copy changed files - if (debugLog) { - console.log( - "🔥 HMR update - copying only changed files for:", - message.changedFiles, - ); - } - - // For HTML template changes, we need to copy the component files that were rebuilt - let filesToCopy = message.emittedFiles; - - // If we have HTML changes, identify which component files need copying - const hasHTMLChanges = message.changedFiles.some((f) => - f.endsWith(".html"), - ); - if (hasHTMLChanges) { - // Copy component-related files (the ones that would have been rebuilt due to template changes) - filesToCopy = message.emittedFiles.filter( - (f) => - f.includes(".component") || - f === "bundle.mjs" || - f === "bundle.mjs.map", - ); - if (debugLog) { - console.log( - "🔥 HTML change detected - copying component files:", - filesToCopy, - ); - } - } - - this.copyViteBundleToNative(distOutput, destDir, filesToCopy); - } else if ( + if ( message.buildType === "incremental" && - message.changedFiles && - message.changedFiles.length > 0 + message.emittedFiles && + message.emittedFiles.length > 0 ) { // Incremental builds: only copy files that are likely affected by the changes - if (debugLog) { - console.log( - "🔥 Incremental build - copying only relevant files for:", - message.changedFiles, - ); - } - const filesToCopy = this.getIncrementalFilesToCopy( message.emittedFiles, - message.changedFiles, ); if (debugLog) { - console.log( - "🔥 Incremental build - files to copy:", - filesToCopy, - ); + console.log("Incremental build - files to copy:", filesToCopy); } this.copyViteBundleToNative(distOutput, destDir, filesToCopy); } else { if (debugLog) { - console.log("🔥 Full build - copying all files"); + console.log("Full build - copying all files."); } this.copyViteBundleToNative(distOutput, destDir); } @@ -236,31 +197,14 @@ export class BundlerCompilerService this.$logger.info( `Vite build completed! Files copied to native platform.`, ); - // Send HMR notification to connected WebSocket clients first - this.notifyHMRClients({ - type: message.isHMR ? "js-update" : "build-complete", - timestamp: Date.now(), - changedFiles: message.changedFiles || [], - buildType: message.buildType || "incremental", - isHMR: message.isHMR || false, - }); - - if (message.isHMR) { - if (debugLog) { - console.log( - "🔥 Skipping BUNDLER_COMPILATION_COMPLETE for HMR update - app will not restart", - ); - } - } else { - // Only emit BUNDLER_COMPILATION_COMPLETE for non-HMR builds - // This prevents the CLI from restarting the app during HMR updates - if (debugLog) { - console.log( - "🔥 Emitting BUNDLER_COMPILATION_COMPLETE for full build", - ); - } - this.emit(BUNDLER_COMPILATION_COMPLETE, data); + + if (debugLog) { + console.log( + "Emitting BUNDLER_COMPILATION_COMPLETE for full build.", + ); } + this.emit(BUNDLER_COMPILATION_COMPLETE, data); + return; } @@ -510,7 +454,7 @@ export class BundlerCompilerService // Note: With Vite, we need `--` to prevent vite cli from erroring on unknown options. const envParams = isVite ? [ - `--mode=${platformData.platformNameLowerCase}`, + `--mode=${prepareData.release ? "production" : "development"}`, `--watch`, "--", ...cliArgs, @@ -530,7 +474,7 @@ export class BundlerCompilerService const args = [ ...additionalNodeArgs, this.getBundlerExecutablePath(projectData), - isVite ? "build" : this.isModernBundler(projectData) ? `build` : null, + isVite || this.isModernBundler(projectData) ? "build" : null, `--config=${projectData.bundlerConfigPath}`, ...envParams, ].filter(Boolean); @@ -910,55 +854,53 @@ export class BundlerCompilerService ) { // Clean and copy Vite output to native platform folder if (debugLog) { - console.log(`Copying Vite bundle from "${distOutput}" to "${destDir}"`); + console.log(`Copying Vite bundle from "${distOutput}" to "${destDir}".`); } - const fs = require("fs"); - try { if (specificFiles) { - // Selective mode: only copy specific files (HMR or incremental) + // Selective mode: only copy specific files (incremental) if (debugLog) { console.log( - "🔥 Selective copy - copying specific files:", + "Selective copy - copying specific files:", specificFiles, ); } // Ensure destination directory exists - fs.mkdirSync(destDir, { recursive: true }); + mkdirSync(destDir, { recursive: true }); // Copy only the specified files for (const file of specificFiles) { const srcPath = path.join(distOutput, file); const destPath = path.join(destDir, file); - if (!fs.existsSync(srcPath)) continue; + if (!existsSync(srcPath)) continue; // create parent dirs - fs.mkdirSync(path.dirname(destPath), { recursive: true }); + mkdirSync(path.dirname(destPath), { recursive: true }); - fs.copyFileSync(srcPath, destPath); + copyFileSync(srcPath, destPath); if (debugLog) { - console.log(`🔥 Copied ${file}`); + console.log(`Copied ${file}`); } } } else { // Full build mode: clean and copy everything if (debugLog) { - console.log("🔥 Full build: Copying all files"); + console.log("Full build: Copying all files."); } // Clean destination directory - if (fs.existsSync(destDir)) { - fs.rmSync(destDir, { recursive: true, force: true }); + if (existsSync(destDir)) { + rmSync(destDir, { recursive: true, force: true }); } - fs.mkdirSync(destDir, { recursive: true }); + mkdirSync(destDir, { recursive: true }); // Copy all files from dist to platform destination - if (fs.existsSync(distOutput)) { - this.copyRecursiveSync(distOutput, destDir, fs); + if (existsSync(distOutput)) { + this.copyRecursiveSync(distOutput, destDir); } else { this.$logger.warn( `Vite output directory does not exist: ${distOutput}`, @@ -970,51 +912,31 @@ export class BundlerCompilerService } } - private getIncrementalFilesToCopy( - emittedFiles: string[], - changedFiles: string[], - ): string[] { + private getIncrementalFilesToCopy(emittedFiles: string[]): string[] { // For incremental builds, we need to determine which emitted files are likely affected // by the source file changes const filesToCopy: string[] = []; - // Always copy bundle files as they contain the compiled source code - // ignoring vendor files as they are less likely to change frequently + // default to ignoring vendor files as they are less likely to change during live reloads const bundleFiles = emittedFiles.filter( (file) => !file.includes("vendor") && - (file.includes("bundle") || - file.includes("main") || - file.includes("app") || - file.endsWith(".mjs") || - file.endsWith(".js")), + (file.endsWith(".mjs") || + file.endsWith(".js") || + file.endsWith(".map")), ); filesToCopy.push(...bundleFiles); - // Always copy source maps for debugging - const sourceMapFiles = emittedFiles.filter( - (file) => !file.includes("vendor") && file.endsWith(".map"), - ); - filesToCopy.push(...sourceMapFiles); - - // Only handle assets if they're explicitly referenced in the changed files - const hasAssetChanges = changedFiles.some( + // Only copy assets if there are explicit asset-related changes + const assetFiles = emittedFiles.filter( (file) => - file.includes("/assets/") || - file.includes("/static/") || - file.includes("/public/"), + file.includes("assets/") || + file.includes("static/") || + file.includes("fonts/") || + file.includes("images/"), ); - - if (hasAssetChanges) { - // Only copy assets if there are explicit asset-related changes - const assetFiles = emittedFiles.filter( - (file) => - file.includes("assets/") || - file.includes("static/") || - file.includes("fonts/") || - file.includes("images/"), - ); + if (assetFiles.length > 0) { filesToCopy.push(...assetFiles); } @@ -1022,48 +944,16 @@ export class BundlerCompilerService return [...new Set(filesToCopy)]; } - private notifyHMRClients(message: any) { - // Send WebSocket notification to HMR clients - try { - const WebSocket = require("ws"); - - // Try to connect to HMR bridge and send notification - const ws = new WebSocket("ws://localhost:24678"); - - ws.on("open", () => { - if (debugLog) { - console.log("🔥 Sending HMR notification to bridge:", message.type); - } - ws.send(JSON.stringify(message)); - ws.close(); - }); - - ws.on("error", () => { - // HMR bridge not available, which is fine - if (debugLog) { - console.log( - "🔥 HMR bridge not available (this is normal without HMR)", - ); - } - }); - } catch (error) { - // WebSocket not available, which is fine - if (debugLog) { - console.log("🔥 WebSocket not available for HMR notifications"); - } - } - } - - private copyRecursiveSync(src: string, dest: string, fs: any) { - for (const entry of fs.readdirSync(src, { withFileTypes: true })) { + private copyRecursiveSync(src: string, dest: string) { + for (const entry of readdirSync(src, { withFileTypes: true })) { const srcPath = path.join(src, entry.name); const destPath = path.join(dest, entry.name); if (entry.isDirectory()) { - fs.mkdirSync(destPath, { recursive: true }); - this.copyRecursiveSync(srcPath, destPath, fs); + mkdirSync(destPath, { recursive: true }); + this.copyRecursiveSync(srcPath, destPath); } else if (entry.isFile() || entry.isSymbolicLink()) { - fs.copyFileSync(srcPath, destPath); + copyFileSync(srcPath, destPath); } } } diff --git a/package.json b/package.json index 2641073dca..fcd7728cce 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "nativescript", "main": "./lib/nativescript-cli-lib.js", - "version": "9.0.0", + "version": "9.0.0-alpha.13", "author": "NativeScript ", "description": "Command-line interface for building NativeScript projects", "bin": { @@ -54,12 +54,12 @@ "javascript" ], "dependencies": { - "@foxt/js-srp": "^0.0.3-patch2", + "@foxt/js-srp": "0.0.3-patch2", "@nativescript/doctor": "2.0.17", - "@npmcli/arborist": "^9.1.4", + "@npmcli/arborist": "9.1.4", "@rigor789/resolve-package-path": "1.0.7", "@nstudio/trapezedev-project": "7.2.3", - "archiver": "^7.0.1", + "archiver": "7.0.1", "axios": "1.11.0", "byline": "5.0.0", "chokidar": "4.0.3",