Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Fix PackageToJS plugin wasm-opt fallback when output file exists
When wasm-opt is not installed, DefaultPackagingSystem falls back to copying
the input Wasm file directly to output. However, FileManager.copyItem
fails if the destination file already exists, while wasm-opt overwrites
existing files.

Changes:
- Remove existing output file before copying in wasm-opt fallback
- Make DefaultPackagingSystem injectable for testing with custom which function
- Add focused tests for DefaultPackagingSystem fallback behavior
- Test both new and existing output file scenarios

Fixes issue where builds fail with 'File exists' error when wasm-opt
is unavailable and output file already exists from previous builds.
  • Loading branch information
kateinoigakukun committed Aug 9, 2025
commit b579fb1e83c73199ab7b8910ea63ccaf1623d5e8
8 changes: 7 additions & 1 deletion Plugins/PackageToJS/Sources/PackageToJS.swift
Original file line number Diff line number Diff line change
Expand Up @@ -293,9 +293,11 @@ extension PackagingSystem {
final class DefaultPackagingSystem: PackagingSystem {

private let printWarning: (String) -> Void
private let which: (String) throws -> URL

init(printWarning: @escaping (String) -> Void) {
init(printWarning: @escaping (String) -> Void, which: @escaping (String) throws -> URL = which(_:)) {
self.printWarning = printWarning
self.which = which
}

func npmInstall(packageDir: String) throws {
Expand All @@ -309,6 +311,10 @@ final class DefaultPackagingSystem: PackagingSystem {
func wasmOpt(_ arguments: [String], input: String, output: String) throws {
guard let wasmOpt = try? which("wasm-opt") else {
_ = warnMissingWasmOpt
// Remove existing output file if it exists (to match wasm-opt behavior)
if FileManager.default.fileExists(atPath: output) {
try FileManager.default.removeItem(atPath: output)
}
try FileManager.default.copyItem(atPath: input, toPath: output)
return
}
Expand Down
60 changes: 60 additions & 0 deletions Plugins/PackageToJS/Tests/DefaultPackagingSystemTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import Foundation
import Testing

@testable import PackageToJS

@Suite struct DefaultPackagingSystemTests {

@Test func wasmOptFallbackHandlesNewOutputFile() throws {
try withTemporaryDirectory { tempDir, _ in
let inputFile = tempDir.appendingPathComponent("input.wasm")
let outputFile = tempDir.appendingPathComponent("output.wasm")
let inputContent = Data("input wasm content".utf8)
try inputContent.write(to: inputFile)

var warnings: [String] = []
// Create system with mock which function that always fails to find wasm-opt
let system = DefaultPackagingSystem(
printWarning: { warnings.append($0) },
which: { _ in throw PackageToJSError("wasm-opt not found") }
)

// This should work - fallback should copy file
try system.wasmOpt(["-Os"], input: inputFile.path, output: outputFile.path)

// Verify the output file was created with input content
let finalContent = try Data(contentsOf: outputFile)
#expect(finalContent == inputContent)
#expect(warnings.contains { $0.contains("wasm-opt is not installed") })
}
}

@Test func wasmOptFallbackHandlesExistingOutputFile() throws {
try withTemporaryDirectory { tempDir, _ in
let inputFile = tempDir.appendingPathComponent("input.wasm")
let outputFile = tempDir.appendingPathComponent("output.wasm")
let inputContent = Data("input wasm content".utf8)
let existingContent = Data("existing output content".utf8)

// Create input file and existing output file
try inputContent.write(to: inputFile)
try existingContent.write(to: outputFile)

var warnings: [String] = []
// Create system with mock which function that always fails to find wasm-opt
let system = DefaultPackagingSystem(
printWarning: { warnings.append($0) },
which: { _ in throw PackageToJSError("wasm-opt not found") }
)

// This should work - fallback should overwrite existing file
try system.wasmOpt(["-Os"], input: inputFile.path, output: outputFile.path)

// Verify the output file was overwritten with input content
let finalContent = try Data(contentsOf: outputFile)
#expect(finalContent == inputContent)
#expect(finalContent != existingContent)
#expect(warnings.contains { $0.contains("wasm-opt is not installed") })
}
}
}