Merge remote-tracking branch 'origin/main'
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -6,6 +6,7 @@ coverage
|
|||||||
.pnpm-store
|
.pnpm-store
|
||||||
**/.DS_Store
|
**/.DS_Store
|
||||||
apps/macos/.build/
|
apps/macos/.build/
|
||||||
|
apps/shared/ClawdisKit/.build/
|
||||||
bin/clawdis-mac
|
bin/clawdis-mac
|
||||||
apps/macos/.build-local/
|
apps/macos/.build-local/
|
||||||
apps/macos/.swiftpm/
|
apps/macos/.swiftpm/
|
||||||
|
|||||||
@@ -487,7 +487,7 @@ struct OnboardingView: View {
|
|||||||
.keyboardShortcut(.return)
|
.keyboardShortcut(.return)
|
||||||
.buttonStyle(.borderedProminent)
|
.buttonStyle(.borderedProminent)
|
||||||
}
|
}
|
||||||
.padding(.horizontal, 20)
|
.padding(.horizontal, 28)
|
||||||
.padding(.bottom, 12)
|
.padding(.bottom, 12)
|
||||||
.frame(height: 60)
|
.frame(height: 60)
|
||||||
}
|
}
|
||||||
@@ -497,6 +497,7 @@ struct OnboardingView: View {
|
|||||||
content()
|
content()
|
||||||
Spacer()
|
Spacer()
|
||||||
}
|
}
|
||||||
|
.padding(.horizontal, 28)
|
||||||
.frame(width: self.pageWidth, alignment: .top)
|
.frame(width: self.pageWidth, alignment: .top)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,4 +18,7 @@ let package = Package(
|
|||||||
swiftSettings: [
|
swiftSettings: [
|
||||||
.enableUpcomingFeature("StrictConcurrency"),
|
.enableUpcomingFeature("StrictConcurrency"),
|
||||||
]),
|
]),
|
||||||
|
.testTarget(
|
||||||
|
name: "ClawdisKitTests",
|
||||||
|
dependencies: ["ClawdisKit"]),
|
||||||
])
|
])
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import ClawdisKit
|
||||||
|
import XCTest
|
||||||
|
|
||||||
|
final class BonjourEscapesTests: XCTestCase {
|
||||||
|
func testDecodePassThrough() {
|
||||||
|
XCTAssertEqual(BonjourEscapes.decode("hello"), "hello")
|
||||||
|
XCTAssertEqual(BonjourEscapes.decode(""), "")
|
||||||
|
}
|
||||||
|
|
||||||
|
func testDecodeSpaces() {
|
||||||
|
XCTAssertEqual(BonjourEscapes.decode("Clawdis\\032Gateway"), "Clawdis Gateway")
|
||||||
|
}
|
||||||
|
|
||||||
|
func testDecodeMultipleEscapes() {
|
||||||
|
XCTAssertEqual(
|
||||||
|
BonjourEscapes.decode("A\\038B\\047C\\032D"),
|
||||||
|
"A&B/C D")
|
||||||
|
}
|
||||||
|
|
||||||
|
func testDecodeIgnoresInvalidEscapeSequences() {
|
||||||
|
XCTAssertEqual(BonjourEscapes.decode("Hello\\03World"), "Hello\\03World")
|
||||||
|
XCTAssertEqual(BonjourEscapes.decode("Hello\\XYZWorld"), "Hello\\XYZWorld")
|
||||||
|
}
|
||||||
|
|
||||||
|
func testDecodeUsesDecimalUnicodeScalarValue() {
|
||||||
|
XCTAssertEqual(BonjourEscapes.decode("Hello\\065World"), "HelloAWorld")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -22,6 +22,7 @@ This flow lets the macOS app act as a full remote control for a Clawdis gateway
|
|||||||
1) Open *Settings → General*.
|
1) Open *Settings → General*.
|
||||||
2) Under **Clawdis runs**, pick **Remote over SSH** and set:
|
2) Under **Clawdis runs**, pick **Remote over SSH** and set:
|
||||||
- **SSH target**: `user@host` (optional `:port`).
|
- **SSH target**: `user@host` (optional `:port`).
|
||||||
|
- If the gateway is on the same LAN and advertises Bonjour, pick it from the discovered list to auto-fill this field.
|
||||||
- **Identity file** (advanced): path to your key.
|
- **Identity file** (advanced): path to your key.
|
||||||
- **Project root** (advanced): remote checkout path used for commands.
|
- **Project root** (advanced): remote checkout path used for commands.
|
||||||
3) Hit **Test remote**. Success indicates the remote `clawdis status --json` runs correctly. Failures usually mean PATH/CLI issues; exit 127 means the CLI isn’t found remotely.
|
3) Hit **Test remote**. Success indicates the remote `clawdis status --json` runs correctly. Failures usually mean PATH/CLI issues; exit 127 means the CLI isn’t found remotely.
|
||||||
|
|||||||
@@ -18,6 +18,11 @@ vi.mock("@homebridge/ciao", () => {
|
|||||||
const { startGatewayBonjourAdvertiser } = await import("./bonjour.js");
|
const { startGatewayBonjourAdvertiser } = await import("./bonjour.js");
|
||||||
|
|
||||||
describe("gateway bonjour advertiser", () => {
|
describe("gateway bonjour advertiser", () => {
|
||||||
|
type ServiceCall = {
|
||||||
|
name?: unknown;
|
||||||
|
txt?: unknown;
|
||||||
|
};
|
||||||
|
|
||||||
const prevEnv = { ...process.env };
|
const prevEnv = { ...process.env };
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
@@ -84,4 +89,30 @@ describe("gateway bonjour advertiser", () => {
|
|||||||
expect(destroy).toHaveBeenCalledTimes(2);
|
expect(destroy).toHaveBeenCalledTimes(2);
|
||||||
expect(shutdown).toHaveBeenCalledTimes(1);
|
expect(shutdown).toHaveBeenCalledTimes(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("normalizes hostnames with domains for service names", async () => {
|
||||||
|
// Allow advertiser to run in unit tests.
|
||||||
|
delete process.env.VITEST;
|
||||||
|
process.env.NODE_ENV = "development";
|
||||||
|
|
||||||
|
vi.spyOn(os, "hostname").mockReturnValue("Mac.localdomain");
|
||||||
|
|
||||||
|
const destroy = vi.fn().mockResolvedValue(undefined);
|
||||||
|
const advertise = vi.fn().mockResolvedValue(undefined);
|
||||||
|
createService.mockReturnValue({ advertise, destroy });
|
||||||
|
|
||||||
|
const started = await startGatewayBonjourAdvertiser({
|
||||||
|
gatewayPort: 18789,
|
||||||
|
sshPort: 2222,
|
||||||
|
bridgePort: 18790,
|
||||||
|
});
|
||||||
|
|
||||||
|
const [masterCall] = createService.mock.calls as Array<[ServiceCall]>;
|
||||||
|
expect(masterCall?.[0]?.name).toBe("Mac (Clawdis)");
|
||||||
|
expect((masterCall?.[0]?.txt as Record<string, string>)?.lanHost).toBe(
|
||||||
|
"Mac.local",
|
||||||
|
);
|
||||||
|
|
||||||
|
await started.stop();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -39,7 +39,15 @@ export async function startGatewayBonjourAdvertiser(
|
|||||||
const { getResponder, Protocol } = await import("@homebridge/ciao");
|
const { getResponder, Protocol } = await import("@homebridge/ciao");
|
||||||
const responder = getResponder();
|
const responder = getResponder();
|
||||||
|
|
||||||
const hostname = os.hostname().replace(/\.local$/i, "");
|
// mDNS service instance names are single DNS labels; dots in hostnames (like
|
||||||
|
// `Mac.localdomain`) can confuse some resolvers/browsers and break discovery.
|
||||||
|
// Keep only the first label and normalize away a trailing `.local`.
|
||||||
|
const hostname =
|
||||||
|
os
|
||||||
|
.hostname()
|
||||||
|
.replace(/\.local$/i, "")
|
||||||
|
.split(".")[0]
|
||||||
|
.trim() || "clawdis";
|
||||||
const instanceName =
|
const instanceName =
|
||||||
typeof opts.instanceName === "string" && opts.instanceName.trim()
|
typeof opts.instanceName === "string" && opts.instanceName.trim()
|
||||||
? opts.instanceName.trim()
|
? opts.instanceName.trim()
|
||||||
|
|||||||
Reference in New Issue
Block a user