Skip to content

Commit 32362f5

Browse files
committed
fix(providers): extract Windows ARM x64 ZIP fallbacks with bsdtar
1 parent 471074f commit 32362f5

2 files changed

Lines changed: 98 additions & 2 deletions

File tree

‎packages/provider-acp-registry/src/server/AcpRegistrySupport.test.ts‎

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ import * as FileSystem from "effect/FileSystem";
1414
import * as Path from "effect/Path";
1515
import * as Fiber from "effect/Fiber";
1616
import * as Layer from "effect/Layer";
17+
import * as Sink from "effect/Sink";
18+
import * as Stream from "effect/Stream";
1719
import * as Schema from "effect/Schema";
1820
import * as HttpClient from "effect/http/HttpClient";
1921
import * as HttpClientResponse from "effect/http/HttpClientResponse";
@@ -823,6 +825,90 @@ describe("AcpRegistrySupport", () => {
823825
);
824826
});
825827

828+
it.effect("prepares an x64 ZIP fallback on Windows ARM when PATH tar cannot read ZIP", () => {
829+
const downloadUrl = "https://registry.test/agent-windows-x64.zip";
830+
const agent = makeAgent({
831+
binary: { "windows-x86_64": { archive: downloadUrl, cmd: "./agent.exe" } },
832+
npx: { package: "@example/[email protected]" },
833+
});
834+
return Effect.gen(function* () {
835+
const fileSystem = yield* FileSystem.FileSystem;
836+
const path = yield* Path.Path;
837+
const cacheDir = yield* fileSystem.makeTempDirectoryScoped({ prefix: "t3-acp-arm-zip-" });
838+
const commands: Array<string> = [];
839+
const spawner = ChildProcessSpawner.make((command) =>
840+
Effect.gen(function* () {
841+
if (command._tag !== "StandardCommand") return yield* Effect.die("Unexpected pipeline");
842+
commands.push(command.command);
843+
// Simulate Git's GNU tar rejecting ZIP and System32 bsdtar listing/extracting it.
844+
const supportsZip = command.command === "C:\\Windows\\System32\\tar.exe";
845+
if (supportsZip && command.args[0] === "-xf") {
846+
const outputDirectory = command.args[3];
847+
if (!outputDirectory) return yield* Effect.die("Missing extraction directory");
848+
yield* fileSystem.writeFileString(path.join(outputDirectory, "agent.exe"), "fixture");
849+
}
850+
return ChildProcessSpawner.makeHandle({
851+
pid: ChildProcessSpawner.ProcessId(1),
852+
exitCode: Effect.succeed(ChildProcessSpawner.ExitCode(supportsZip ? 0 : 1)),
853+
isRunning: Effect.succeed(false),
854+
kill: () => Effect.void,
855+
unref: Effect.succeed(Effect.void),
856+
stdin: Sink.drain,
857+
stdout: Stream.make(new TextEncoder().encode(supportsZip ? "agent.exe\r\n" : "")),
858+
stderr: Stream.make(
859+
new TextEncoder().encode(supportsZip ? "" : "GNU tar cannot read ZIP"),
860+
),
861+
all: Stream.empty,
862+
getInputFd: () => Sink.drain,
863+
getOutputFd: () => Stream.empty,
864+
});
865+
}).pipe(Effect.orDie),
866+
);
867+
const resolver = yield* AcpRegistrySupport.makeAcpRegistryCatalog({
868+
cacheDir,
869+
toolsDir: path.join(cacheDir, "tools"),
870+
registryUrl,
871+
}).pipe(
872+
Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, spawner),
873+
Effect.provideService(HostProcess.Platform, "win32"),
874+
Effect.provideService(HostProcess.Architecture, "arm64"),
875+
Effect.provideService(HostProcess.Environment, {
876+
SystemRoot: "C:\\Windows",
877+
PATH: "C:\\Program Files\\Git\\usr\\bin;C:\\Windows\\System32",
878+
}),
879+
Effect.provideService(SpawnExecutableResolution, (command) =>
880+
command === "tar" ? "C:\\Program Files\\Git\\usr\\bin\\tar.exe" : command,
881+
),
882+
);
883+
yield* resolver.prepare({ agentId: agent.id });
884+
const resolved = yield* resolver.resolve(settings(), "/workspace", {});
885+
expect(resolved.spawn.command).toBe(
886+
yield* fileSystem.realPath(
887+
path.join(cacheDir, "tools", agent.id, agent.version, "windows-x86_64", "agent.exe"),
888+
),
889+
);
890+
expect(yield* fileSystem.readFileString(resolved.spawn.command)).toBe("fixture");
891+
expect(commands).toEqual([
892+
"C:\\Windows\\System32\\tar.exe",
893+
"C:\\Windows\\System32\\tar.exe",
894+
]);
895+
}).pipe(
896+
Effect.scoped,
897+
Effect.provide(
898+
layerResolver((request) =>
899+
Effect.succeed(
900+
HttpClientResponse.fromWeb(
901+
request,
902+
request.url === registryUrl
903+
? new Response(makeRegistry(agent))
904+
: new Response("ZIP fixture"),
905+
),
906+
),
907+
),
908+
),
909+
);
910+
});
911+
826912
it.effect("searches compatible agents with deterministic ranking and bounded metadata", () => {
827913
const exact = {
828914
...makeAgent({ npx: { package: "@example/[email protected]" } }),

‎packages/provider-acp-registry/src/server/AcpRegistrySupport.ts‎

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
} from "@t3tools/contracts";
1313
import type { AcpRegistryDistributionPreference } from "../settings.ts";
1414
import type { AcpRegistrySettings } from "../settings.ts";
15+
import { cliArchiveTarCommand } from "@t3tools/shared/cliRelease";
1516
import * as HostProcess from "@t3tools/shared/HostProcess";
1617
import {
1718
mergePathEntries,
@@ -1363,6 +1364,7 @@ export const makeAcpRegistryCatalog = Effect.fn("AcpRegistryCatalog.make")(funct
13631364
return {
13641365
commandSegments,
13651366
installRoot,
1367+
platformTarget: matchedPlatformTarget,
13661368
executablePath: path.join(installRoot, ...commandSegments),
13671369
};
13681370
};
@@ -1509,6 +1511,14 @@ export const makeAcpRegistryCatalog = Effect.fn("AcpRegistryCatalog.make")(funct
15091511
yield* fileSystem.makeDirectory(path.dirname(targetPath), { recursive: true });
15101512
yield* fileSystem.rename(archivePath, targetPath);
15111513
} else {
1514+
// The new Windows ARM fallback must extract x64 ZIPs even when Git's GNU tar wins PATH.
1515+
// Keep archive selection for existing native distributions unchanged in this PR.
1516+
const tarCommand =
1517+
platformTarget === "windows-aarch64" &&
1518+
paths.platformTarget === "windows-x86_64" &&
1519+
kind === "zip"
1520+
? cliArchiveTarCommand(platform, hostEnvironment)
1521+
: "tar";
15121522
const listArgs =
15131523
kind === "tar_gz"
15141524
? ["-tzf", archivePath]
@@ -1520,7 +1530,7 @@ export const makeAcpRegistryCatalog = Effect.fn("AcpRegistryCatalog.make")(funct
15201530
const entries =
15211531
listArgs === undefined
15221532
? yield* runCommand("unzip", ["-Z1", archivePath])
1523-
: yield* runCommand("tar", listArgs);
1533+
: yield* runCommand(tarCommand, listArgs);
15241534
if (!validateArchiveEntries(entries)) {
15251535
return yield* new AcpRegistryError({
15261536
reason: "archive_invalid",
@@ -1531,7 +1541,7 @@ export const makeAcpRegistryCatalog = Effect.fn("AcpRegistryCatalog.make")(funct
15311541
yield* runCommand("unzip", ["-q", archivePath, "-d", extractionRoot]);
15321542
} else {
15331543
const extractFlag = kind === "tar_gz" ? "-xzf" : kind === "tar_bz2" ? "-xjf" : "-xf";
1534-
yield* runCommand("tar", [extractFlag, archivePath, "-C", extractionRoot]);
1544+
yield* runCommand(tarCommand, [extractFlag, archivePath, "-C", extractionRoot]);
15351545
}
15361546
}
15371547

0 commit comments

Comments
 (0)