Skip to content

Commit 7a2efaf

Browse files
tannerlinsleyNathanWalker
authored andcommitted
fix: copy Vite output for production builds
1 parent 5f776b3 commit 7a2efaf

2 files changed

Lines changed: 118 additions & 30 deletions

File tree

lib/services/bundler/bundler-compiler-service.ts

Lines changed: 45 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,19 @@ export class BundlerCompilerService
9090
);
9191
}
9292

93+
private getViteBuildPaths(
94+
platformData: IPlatformData,
95+
projectData: IProjectData,
96+
) {
97+
return {
98+
distOutput: this.getViteDistOutputPath(projectData.projectDir),
99+
destDir: path.join(
100+
platformData.appDestinationDirectoryPath,
101+
this.$options.hostProjectModuleName,
102+
),
103+
};
104+
}
105+
93106
public async compileWithWatch(
94107
platformData: IPlatformData,
95108
projectData: IProjectData,
@@ -158,12 +171,9 @@ export class BundlerCompilerService
158171
}
159172

160173
// Copy Vite output files directly to platform destination
161-
const distOutput = this.getViteDistOutputPath(
162-
projectData.projectDir,
163-
);
164-
const destDir = path.join(
165-
platformData.appDestinationDirectoryPath,
166-
this.$options.hostProjectModuleName,
174+
const { distOutput, destDir } = this.getViteBuildPaths(
175+
platformData,
176+
projectData,
167177
);
168178

169179
if (debugLog) {
@@ -428,25 +438,21 @@ export class BundlerCompilerService
428438
// is left empty (or worse, runs stale dev/HMR artifacts from
429439
// a previous `ns debug` run) and the runtime crashes on
430440
// launch with `Check failed: has_pending_exception()`.
431-
if (isVite) {
432-
try {
433-
const distOutput = this.getViteDistOutputPath(
434-
projectData.projectDir,
435-
);
436-
const destDir = path.join(
437-
platformData.appDestinationDirectoryPath,
438-
this.$options.hostProjectModuleName,
439-
);
440-
this.copyViteBundleToNative(distOutput, destDir);
441-
} catch (copyErr) {
442-
this.$logger.warn(
443-
`Failed to copy Vite output to platform destination: ${
444-
(copyErr as Error).message
445-
}`,
441+
// The copy must succeed for the build to succeed — a build
442+
// whose bundle never reached the native app is not a
443+
// successful build, so copy failures reject here.
444+
try {
445+
if (isVite) {
446+
const { distOutput, destDir } = this.getViteBuildPaths(
447+
platformData,
448+
projectData,
446449
);
450+
this.copyViteBundleToNative(distOutput, destDir, null, true);
447451
}
452+
resolve();
453+
} catch (error) {
454+
reject(error);
448455
}
449-
resolve();
450456
} else {
451457
const error: any = new Error(
452458
`Executing ${projectData.bundler} failed with exit code ${exitCode}.`,
@@ -1102,6 +1108,7 @@ export class BundlerCompilerService
11021108
distOutput: string,
11031109
destDir: string,
11041110
specificFiles: string[] = null,
1111+
failOnError = false,
11051112
) {
11061113
// Clean and copy Vite output to native platform folder
11071114
if (debugLog) {
@@ -1143,23 +1150,31 @@ export class BundlerCompilerService
11431150
console.log("Full build: Copying all files.");
11441151
}
11451152

1153+
// Validate the source before touching the destination — cleaning
1154+
// destDir first would wipe a previously good bundle and leave an
1155+
// empty app folder behind a missing Vite output.
1156+
if (!this.$fs.exists(distOutput)) {
1157+
throw new Error(
1158+
`Vite output directory does not exist: ${distOutput}`,
1159+
);
1160+
}
1161+
11461162
// Clean destination directory
11471163
if (this.$fs.exists(destDir)) {
11481164
this.$fs.deleteDirectory(destDir);
11491165
}
11501166
this.$fs.createDirectory(destDir);
11511167

11521168
// Copy all files from dist to platform destination
1153-
if (this.$fs.exists(distOutput)) {
1154-
this.copyRecursiveSync(distOutput, destDir);
1155-
} else {
1156-
this.$logger.warn(
1157-
`Vite output directory does not exist: ${distOutput}`,
1158-
);
1159-
}
1169+
this.copyRecursiveSync(distOutput, destDir);
11601170
}
11611171
} catch (error) {
1162-
this.$logger.warn(`Failed to copy Vite bundle: ${error.message}`);
1172+
const copyError =
1173+
error instanceof Error ? error : new Error(String(error));
1174+
if (failOnError) {
1175+
throw copyError;
1176+
}
1177+
this.$logger.warn(`Failed to copy Vite bundle: ${copyError.message}`);
11631178
}
11641179
}
11651180

test/services/bundler/bundler-compiler-service.ts

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -337,6 +337,79 @@ describe("BundlerCompilerService", () => {
337337
});
338338

339339
describe("compileWithoutWatch", () => {
340+
it("copies a successful Vite build to the native app", async () => {
341+
const previous = process.env.NS_VITE_DIST_DIR;
342+
delete process.env.NS_VITE_DIST_DIR;
343+
try {
344+
const childProcess = Object.assign(new EventEmitter(), { pid: 1234 });
345+
const copies: Array<{
346+
distOutput: string;
347+
destDir: string;
348+
failOnError: boolean;
349+
}> = [];
350+
testInjector.resolve("options").hostProjectModuleName = "app";
351+
(<any>bundlerCompilerService).getBundler = () => "vite";
352+
(<any>bundlerCompilerService).startBundleProcess = async () =>
353+
childProcess;
354+
(<any>bundlerCompilerService).copyViteBundleToNative = (
355+
distOutput: string,
356+
destDir: string,
357+
_specificFiles: string[],
358+
failOnError: boolean,
359+
) => {
360+
copies.push({ distOutput, destDir, failOnError });
361+
};
362+
363+
const compilation = bundlerCompilerService.compileWithoutWatch(
364+
<any>{
365+
platformNameLowerCase: "android",
366+
appDestinationDirectoryPath: "/project/platforms/android",
367+
},
368+
<any>{ projectDir: "/project" },
369+
<any>{},
370+
);
371+
setImmediate(() => childProcess.emit("close", 0));
372+
await compilation;
373+
374+
assert.deepEqual(copies, [
375+
{
376+
distOutput: path.join("/project", ".ns-vite-build"),
377+
destDir: path.join("/project/platforms/android", "app"),
378+
failOnError: true,
379+
},
380+
]);
381+
} finally {
382+
if (previous === undefined) {
383+
delete process.env.NS_VITE_DIST_DIR;
384+
} else {
385+
process.env.NS_VITE_DIST_DIR = previous;
386+
}
387+
}
388+
});
389+
390+
it("fails when a successful Vite build cannot be copied", async () => {
391+
const childProcess = Object.assign(new EventEmitter(), { pid: 1234 });
392+
testInjector.resolve("options").hostProjectModuleName = "app";
393+
(<any>bundlerCompilerService).getBundler = () => "vite";
394+
(<any>bundlerCompilerService).startBundleProcess = async () =>
395+
childProcess;
396+
(<any>bundlerCompilerService).copyViteBundleToNative = () => {
397+
throw new Error("copy failed");
398+
};
399+
400+
const compilation = bundlerCompilerService.compileWithoutWatch(
401+
<any>{
402+
platformNameLowerCase: "ios",
403+
appDestinationDirectoryPath: "/project/platforms/ios",
404+
},
405+
<any>{ projectDir: "/project" },
406+
<any>{},
407+
);
408+
setImmediate(() => childProcess.emit("close", 0));
409+
410+
await assert.isRejected(compilation, "copy failed");
411+
});
412+
340413
it("fails when the value set for bundlerConfigPath is not existant file", async () => {
341414
const bundlerConfigPath = "some path.js";
342415
testInjector.resolve("fs").exists = (filePath: string) =>

0 commit comments

Comments
 (0)