From 5d02f7afb63828b0c1c88ce0f60d54001ede9931 Mon Sep 17 00:00:00 2001 From: Altay Date: Fri, 24 Jul 2026 18:15:21 +0300 Subject: [PATCH] fix(installer): treat "No such file or directory" as an empty developer slot Deleting or replacing an already-empty developer slot on older Roku devices (e.g. Roku Stick 3500X) returns "Delete Failed: No such file or directory" rather than "no plugin installed". isEmptyDeveloperSlotMessage only matched the latter, so deleteInstalledChannel failed on an empty slot and a Replace install would not fall back to a fresh Install. Recognize both phrasings as an empty slot, making delete idempotent and the install Replace->Install fallback work on older devices. Adds unit coverage for the empty-slot phrasings and the delete-success classifier. Co-Authored-By: Claude Opus 4.8 --- src/installer-message.ts | 6 +++++- test/installer-message.test.ts | 19 +++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) create mode 100644 test/installer-message.test.ts diff --git a/src/installer-message.ts b/src/installer-message.ts index d038d0c..d2c17e7 100644 --- a/src/installer-message.ts +++ b/src/installer-message.ts @@ -26,8 +26,12 @@ export const normalizeInstallSuccessMessage = (message: string): string => ? "Identical to previous version -- not replacing" : "Successful deploy"; +// Both are empty-developer-slot signals: newer devices report "no plugin +// installed", while older ones (e.g. Roku Stick 3500X) fail a delete/replace of +// an empty slot with "No such file or directory". Treating either as an empty +// slot makes delete idempotent and lets a Replace fall back to a fresh Install. export const isEmptyDeveloperSlotMessage = (message: string): boolean => - /no plugin installed/i.test(message); + /no plugin installed/i.test(message) || /no such file or directory/i.test(message); export const isDeleteSuccess = (message: string): boolean => isRecognizedInstallerMessage(message) && diff --git a/test/installer-message.test.ts b/test/installer-message.test.ts new file mode 100644 index 0000000..f50c00f --- /dev/null +++ b/test/installer-message.test.ts @@ -0,0 +1,19 @@ +import { describe, expect, it } from "vite-plus/test"; +import { isDeleteSuccess, isEmptyDeveloperSlotMessage } from "../src/installer-message.js"; + +describe("installer message classification", () => { + it("recognizes both empty developer-slot phrasings", () => { + expect(isEmptyDeveloperSlotMessage("No plugin installed")).toBe(true); + // Older devices fail a delete/replace of an empty slot this way. + expect(isEmptyDeveloperSlotMessage("Delete Failed: No such file or directory")).toBe(true); + expect(isEmptyDeveloperSlotMessage("Some unrelated installer message")).toBe(false); + }); + + it("treats delete success and empty-slot deletes as success, rejects real failures", () => { + expect(isDeleteSuccess("Delete Succeeded.")).toBe(true); + expect(isDeleteSuccess("Delete Failed: No such file or directory")).toBe(true); + expect(isDeleteSuccess("No plugin installed")).toBe(true); + expect(isDeleteSuccess("Install Failure: compilation failed")).toBe(false); + expect(isDeleteSuccess("Empty Roku installer response")).toBe(false); + }); +});