From 8091c06e3fa52ca676523f6e8751ac07f3d4d0e8 Mon Sep 17 00:00:00 2001 From: Tanner Linsley Date: Tue, 21 Jul 2026 14:27:21 -0600 Subject: [PATCH] fix: copy Vite output for production builds --- .../bundler/bundler-compiler-service.ts | 58 ++++++++++----- .../bundler/bundler-compiler-service.ts | 71 ++++++++++++++++++- 2 files changed, 111 insertions(+), 18 deletions(-) diff --git a/lib/services/bundler/bundler-compiler-service.ts b/lib/services/bundler/bundler-compiler-service.ts index 3eb880b2ba..7a0c1dc8d9 100644 --- a/lib/services/bundler/bundler-compiler-service.ts +++ b/lib/services/bundler/bundler-compiler-service.ts @@ -126,13 +126,9 @@ export class BundlerCompilerService } // Copy Vite output files directly to platform destination - const distOutput = path.join( - projectData.projectDir, - ".ns-vite-build", - ); - const destDir = path.join( - platformData.appDestinationDirectoryPath, - this.$options.hostProjectModuleName, + const { distOutput, destDir } = this.getViteBuildPaths( + platformData, + projectData, ); if (debugLog) { @@ -380,7 +376,18 @@ export class BundlerCompilerService delete this.bundlerProcesses[platformData.platformNameLowerCase]; const exitCode = typeof arg === "number" ? arg : arg && arg.code; if (exitCode === 0) { - resolve(); + try { + if (this.getBundler() === "vite") { + const { distOutput, destDir } = this.getViteBuildPaths( + platformData, + projectData, + ); + this.copyViteBundleToNative(distOutput, destDir, null, true); + } + resolve(); + } catch (error) { + reject(error); + } } else { const error: any = new Error( `Executing ${projectData.bundler} failed with exit code ${exitCode}.`, @@ -845,10 +852,24 @@ export class BundlerCompilerService return this.$projectConfigService.getValue(`bundler`, "webpack"); } + private getViteBuildPaths( + platformData: IPlatformData, + projectData: IProjectData, + ) { + return { + distOutput: path.join(projectData.projectDir, ".ns-vite-build"), + destDir: path.join( + platformData.appDestinationDirectoryPath, + this.$options.hostProjectModuleName, + ), + }; + } + private copyViteBundleToNative( distOutput: string, destDir: string, specificFiles: string[] = null, + failOnError = false, ) { // Clean and copy Vite output to native platform folder if (debugLog) { @@ -890,6 +911,12 @@ export class BundlerCompilerService console.log("Full build: Copying all files."); } + if (!this.$fs.exists(distOutput)) { + throw new Error( + `Vite output directory does not exist: ${distOutput}`, + ); + } + // Clean destination directory if (this.$fs.exists(destDir)) { this.$fs.deleteDirectory(destDir); @@ -897,16 +924,15 @@ export class BundlerCompilerService this.$fs.createDirectory(destDir); // Copy all files from dist to platform destination - if (this.$fs.exists(distOutput)) { - this.copyRecursiveSync(distOutput, destDir); - } else { - this.$logger.warn( - `Vite output directory does not exist: ${distOutput}`, - ); - } + this.copyRecursiveSync(distOutput, destDir); } } catch (error) { - this.$logger.warn(`Failed to copy Vite bundle: ${error.message}`); + const copyError = + error instanceof Error ? error : new Error(String(error)); + if (failOnError) { + throw copyError; + } + this.$logger.warn(`Failed to copy Vite bundle: ${copyError.message}`); } } diff --git a/test/services/bundler/bundler-compiler-service.ts b/test/services/bundler/bundler-compiler-service.ts index 49d69a55f4..d15b625e11 100644 --- a/test/services/bundler/bundler-compiler-service.ts +++ b/test/services/bundler/bundler-compiler-service.ts @@ -4,6 +4,8 @@ import { assert } from "chai"; import { ErrorsStub } from "../../stubs"; import { IInjector } from "../../../lib/common/definitions/yok"; import { CONFIG_FILE_NAME_DISPLAY } from "../../../lib/constants"; +import { EventEmitter } from "events"; +import * as path from "path"; const iOSPlatformName = "ios"; const androidPlatformName = "android"; @@ -27,12 +29,14 @@ function createTestInjector(): IInjector { testInjector.register("childProcess", {}); testInjector.register("hooksService", {}); testInjector.register("hostInfo", {}); - testInjector.register("options", {}); + testInjector.register("options", { hostProjectModuleName: "app" }); testInjector.register("logger", {}); testInjector.register("errors", ErrorsStub); testInjector.register("packageInstallationManager", {}); testInjector.register("mobileHelper", {}); - testInjector.register("cleanupService", {}); + testInjector.register("cleanupService", { + removeKillProcess: async (): Promise => undefined, + }); testInjector.register("projectConfigService", { getValue: (key: string, defaultValue?: string) => defaultValue, }); @@ -221,6 +225,69 @@ describe("BundlerCompilerService", () => { }); describe("compileWithoutWatch", () => { + it("copies a successful Vite build to the native app", async () => { + const childProcess = Object.assign(new EventEmitter(), { pid: 1234 }); + const copies: Array<{ + distOutput: string; + destDir: string; + failOnError: boolean; + }> = []; + (bundlerCompilerService).startBundleProcess = async () => + childProcess; + (bundlerCompilerService).copyViteBundleToNative = ( + distOutput: string, + destDir: string, + _specificFiles: string[], + failOnError: boolean, + ) => { + copies.push({ distOutput, destDir, failOnError }); + }; + (testInjector.resolve("projectConfigService")).getValue = () => + "vite"; + + const compilation = bundlerCompilerService.compileWithoutWatch( + { + platformNameLowerCase: "android", + appDestinationDirectoryPath: "/project/platforms/android", + }, + { projectDir: "/project" }, + {}, + ); + setImmediate(() => childProcess.emit("close", 0)); + await compilation; + + assert.deepEqual(copies, [ + { + distOutput: path.join("/project", ".ns-vite-build"), + destDir: path.join("/project/platforms/android", "app"), + failOnError: true, + }, + ]); + }); + + it("fails when a successful Vite build cannot be copied", async () => { + const childProcess = Object.assign(new EventEmitter(), { pid: 1234 }); + (bundlerCompilerService).startBundleProcess = async () => + childProcess; + (bundlerCompilerService).copyViteBundleToNative = () => { + throw new Error("copy failed"); + }; + (testInjector.resolve("projectConfigService")).getValue = () => + "vite"; + + const compilation = bundlerCompilerService.compileWithoutWatch( + { + platformNameLowerCase: "ios", + appDestinationDirectoryPath: "/project/platforms/ios", + }, + { projectDir: "/project" }, + {}, + ); + setImmediate(() => childProcess.emit("close", 0)); + + await assert.isRejected(compilation, "copy failed"); + }); + it("fails when the value set for bundlerConfigPath is not existant file", async () => { const bundlerConfigPath = "some path.js"; testInjector.resolve("fs").exists = (filePath: string) =>