diff --git a/Resources/Info.plist b/Resources/Info.plist
index a859496..99479b9 100644
--- a/Resources/Info.plist
+++ b/Resources/Info.plist
@@ -19,9 +19,9 @@
CFBundlePackageType
APPL
CFBundleShortVersionString
- 1.0.4
+ 1.0.5
CFBundleVersion
- 17
+ 18
GlossSafariExtensionAvailable
CFBundleDocumentTypes
diff --git a/Sources/Gloss/BrowserExtensionFiles.swift b/Sources/Gloss/BrowserExtensionFiles.swift
index 37cd751..a2eb4cc 100644
--- a/Sources/Gloss/BrowserExtensionFiles.swift
+++ b/Sources/Gloss/BrowserExtensionFiles.swift
@@ -43,7 +43,9 @@ enum BrowserExtensionFiles {
if fileManager.fileExists(atPath: destination.path) {
let marker = destination.appendingPathComponent(markerName)
- guard fileManager.fileExists(atPath: marker.path) else {
+ let isManaged = fileManager.fileExists(atPath: marker.path)
+ guard try isManaged || isRecognizableLegacyCopy(source: source, destination: destination)
+ else {
throw BrowserExtensionError.unmanagedDestination(destination.path)
}
}
@@ -73,6 +75,17 @@ enum BrowserExtensionFiles {
return destination
}
+ static func isRecognizableLegacyCopy(source: URL, destination: URL) throws -> Bool {
+ let sourceFiles = try relativeFiles(in: source).filter { $0 != markerName }
+ let destinationFiles = try relativeFiles(in: destination).filter { $0 != markerName }
+ guard sourceFiles == destinationFiles else { return false }
+
+ return FileManager.default.contentsEqual(
+ atPath: source.appendingPathComponent("manifest.json").path,
+ andPath: destination.appendingPathComponent("manifest.json").path
+ )
+ }
+
static func synchronizeManagedCopy(source: URL, destination: URL) throws {
let fileManager = FileManager.default
let sourceFiles = try relativeFiles(in: source)
diff --git a/Tests/GlossAppTests/BrowserExtensionFilesTests.swift b/Tests/GlossAppTests/BrowserExtensionFilesTests.swift
index 008c898..bda9e0b 100644
--- a/Tests/GlossAppTests/BrowserExtensionFilesTests.swift
+++ b/Tests/GlossAppTests/BrowserExtensionFilesTests.swift
@@ -3,6 +3,48 @@ import XCTest
@testable import Gloss
final class BrowserExtensionFilesTests: XCTestCase {
+ func testRecognizesUnmarkedBundledCopyForMigration() throws {
+ let root = FileManager.default.temporaryDirectory
+ .appendingPathComponent("gloss-extension-migration-\(UUID().uuidString)", isDirectory: true)
+ let source = root.appendingPathComponent("source", isDirectory: true)
+ let destination = root.appendingPathComponent("destination", isDirectory: true)
+ defer { try? FileManager.default.removeItem(at: root) }
+
+ try FileManager.default.createDirectory(at: source, withIntermediateDirectories: true)
+ try FileManager.default.createDirectory(at: destination, withIntermediateDirectories: true)
+ let manifest = Data(#"{"name":"Gloss","version":"1.0.0"}"#.utf8)
+ try manifest.write(to: source.appendingPathComponent("manifest.json"))
+ try manifest.write(to: destination.appendingPathComponent("manifest.json"))
+ try Data("new".utf8).write(to: source.appendingPathComponent("background.js"))
+ try Data("old".utf8).write(to: destination.appendingPathComponent("background.js"))
+
+ XCTAssertTrue(
+ try BrowserExtensionFiles.isRecognizableLegacyCopy(
+ source: source,
+ destination: destination
+ )
+ )
+
+ try Data(#"{"name":"Other","version":"1.0.0"}"#.utf8).write(
+ to: destination.appendingPathComponent("manifest.json")
+ )
+ XCTAssertFalse(
+ try BrowserExtensionFiles.isRecognizableLegacyCopy(
+ source: source,
+ destination: destination
+ )
+ )
+
+ try manifest.write(to: destination.appendingPathComponent("manifest.json"))
+ try Data("foreign".utf8).write(to: destination.appendingPathComponent("extra.js"))
+ XCTAssertFalse(
+ try BrowserExtensionFiles.isRecognizableLegacyCopy(
+ source: source,
+ destination: destination
+ )
+ )
+ }
+
func testSynchronizeManagedCopyUpdatesFilesWithoutReplacingDirectory() throws {
let root = FileManager.default.temporaryDirectory
.appendingPathComponent("gloss-extension-sync-\(UUID().uuidString)", isDirectory: true)
diff --git a/docs/release-notes/v1.0.5.md b/docs/release-notes/v1.0.5.md
new file mode 100644
index 0000000..70a49a5
--- /dev/null
+++ b/docs/release-notes/v1.0.5.md
@@ -0,0 +1,11 @@
+# Gloss 1.0.5
+
+Gloss 1.0.5 fixes browser extension setup for existing installations.
+
+## Browser extension
+
+- Migrates recognizable legacy extension directories that predate the Gloss management marker.
+- Preserves overwrite protection for unrelated or modified extension directories.
+- Restores the **Show Extension** action without requiring users to delete or reinstall the extension manually.
+
+Gloss 1.0.5 requires macOS 14 or newer.