Skip to content

Commit 3d181e5

Browse files
fix: enhance error handling in populateCache for rclone uploads
1 parent 0d53eac commit 3d181e5

File tree

2 files changed

+75
-6
lines changed

2 files changed

+75
-6
lines changed

packages/cloudflare/src/cli/commands/populate-cache.spec.ts

Lines changed: 61 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ vi.mock("./helpers.js", () => ({
8080
}));
8181

8282
vi.mock("node:child_process", () => ({
83-
spawnSync: vi.fn(() => ({ status: 0 })),
83+
spawnSync: vi.fn(() => ({ status: 0, stderr: Buffer.from("") })),
8484
}));
8585

8686
describe("populateCache", () => {
@@ -177,13 +177,72 @@ describe("populateCache", () => {
177177
// Verify batch upload was used with correct parameters and temporary config
178178
expect(spawnSync).toHaveBeenCalledWith(
179179
"rclone",
180-
expect.arrayContaining(["copy", expect.any(String), "r2:test-bucket"]),
180+
expect.arrayContaining(["copy", expect.any(String), "r2:test-bucket", "--error-on-no-transfer"]),
181181
expect.objectContaining({
182+
stdio: ["inherit", "inherit", "pipe"],
182183
env: expect.objectContaining({
183184
RCLONE_CONFIG: expect.stringMatching(/rclone-config-\d+\.conf$/),
184185
}),
185186
})
186187
);
187188
});
189+
190+
test("handles rclone errors with status > 0", async () => {
191+
const { runWrangler } = await import("../utils/run-wrangler.js");
192+
193+
// Set R2 credentials
194+
vi.stubEnv("R2_ACCESS_KEY_ID", "test_access_key");
195+
vi.stubEnv("R2_SECRET_ACCESS_KEY", "test_secret_key");
196+
vi.stubEnv("R2_ACCOUNT_ID", "test_account_id");
197+
198+
setupMockFileSystem();
199+
200+
// Mock rclone failure without stderr output
201+
vi.mocked(spawnSync).mockReturnValueOnce({
202+
status: 7, // Fatal error exit code
203+
stderr: "", // No stderr output
204+
} as any); // eslint-disable-line @typescript-eslint/no-explicit-any
205+
206+
vi.mocked(runWrangler).mockClear();
207+
208+
await populateCache(
209+
createTestBuildOptions(),
210+
createTestOpenNextConfig() as any, // eslint-disable-line @typescript-eslint/no-explicit-any
211+
createTestWranglerConfig() as any, // eslint-disable-line @typescript-eslint/no-explicit-any
212+
createTestPopulateCacheOptions()
213+
);
214+
215+
// Should fall back to standard upload when batch upload fails
216+
expect(runWrangler).toHaveBeenCalled();
217+
});
218+
});
219+
220+
test("handles rclone errors with status = 0 and stderr output", async () => {
221+
const { runWrangler } = await import("../utils/run-wrangler.js");
222+
223+
// Set R2 credentials
224+
vi.stubEnv("R2_ACCESS_KEY_ID", "test_access_key");
225+
vi.stubEnv("R2_SECRET_ACCESS_KEY", "test_secret_key");
226+
vi.stubEnv("R2_ACCOUNT_ID", "test_account_id");
227+
228+
setupMockFileSystem();
229+
230+
// Mock rclone error in stderr
231+
vi.mocked(spawnSync).mockReturnValueOnce({
232+
status: 0, // non-error exit code
233+
stderr: Buffer.from("ERROR : Failed to copy: AccessDenied: Access Denied (403)"),
234+
} as any); // eslint-disable-line @typescript-eslint/no-explicit-any
235+
236+
vi.mocked(runWrangler).mockClear();
237+
238+
await populateCache(
239+
createTestBuildOptions(),
240+
createTestOpenNextConfig() as any, // eslint-disable-line @typescript-eslint/no-explicit-any
241+
createTestWranglerConfig() as any, // eslint-disable-line @typescript-eslint/no-explicit-any
242+
createTestPopulateCacheOptions()
243+
);
244+
245+
// Should fall back to standard upload when batch upload fails
246+
expect(runWrangler).toHaveBeenCalled();
188247
});
189248
});

packages/cloudflare/src/cli/commands/populate-cache.ts

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -220,15 +220,25 @@ async function populateR2IncrementalCacheWithBatchUpload(
220220

221221
// Use rclone to sync the R2
222222
const remote = `r2:${bucket}`;
223-
const args = ["copy", stagingDir, remote, "--progress", "--transfers=32", "--checkers=16"];
223+
const args = [
224+
"copy",
225+
stagingDir,
226+
remote,
227+
"--progress",
228+
"--transfers=32",
229+
"--checkers=16",
230+
"--error-on-no-transfer", // Fail if no files transferred
231+
];
224232

225233
const result = spawnSync("rclone", args, {
226-
stdio: "inherit",
234+
stdio: ["inherit", "inherit", "pipe"], // Capture stderr while showing stdout
227235
env,
228236
});
229237

230-
if (result.status !== 0) {
231-
throw new Error("Batch upload failed");
238+
// Check for errors in both exit code and stderr
239+
const stderrOutput = result.stderr?.toString().trim();
240+
if (result.status !== 0 || stderrOutput) {
241+
throw new Error("Batch upload failed.");
232242
}
233243

234244
logger.info(`Successfully uploaded ${assets.length} assets to R2 using batch upload`);

0 commit comments

Comments
 (0)