authorgravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-09-04 17:39:11-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-09-04 17:39:11-07:00
log2d5464afbc19058be8c125d5ee70481d721d14f4
tree91b304ce65f75835d7a128d0eefd848a68f70ef2
parentb6f72681da394369274121c4d1216f5d64a7a4bb
signature Signed by PGP key B5690EEEBB952194

fix(server): preserve native provider executable paths during updates (#9850)


2 files changed, 53 insertions(+), 6 deletions(-)

apps/server/src/provider/providerMaintenance.test.ts+50-4
......@@ -1,6 +1,7 @@
11// @effect-diagnostics nodeBuiltinImport:off
22import { expect, it } from "@effect/vitest";
33import * as NodeFS from "node:fs";
4import * as NodeChildProcess from "node:child_process";
45import * as NodeServices from "@effect/platform-node/NodeServices";
56import * as NodeOS from "node:os";
67import * as NodePath from "node:path";
......@@ -8,6 +9,7 @@ import { ProviderDriverKind, ProviderInstanceId, type ServerProvider } from "@t3
89import { HostProcessPlatform } from "@t3tools/shared/hostProcess";
910import * as Crypto from "effect/Crypto";
1011import * as Effect from "effect/Effect";
12import * as FileSystem from "effect/FileSystem";
1113import { HttpClient } from "effect/unstable/http";
1214import {
1315 createProviderVersionAdvisory,
......@@ -362,9 +364,9 @@ it.layer(NodeServices.layer)("providerMaintenance", (it) => {
362364 provider: driver("nativePackageTool"),
363365 packageName: "@example/native-package-tool",
364366 update: {
365 command: "native-package-tool update",
367 command: `${nativePackageToolPath} update`,
366368
367 executable: "native-package-tool",
369 executable: nativePackageToolPath,
368370
369371 args: ["update"],
370372
......@@ -399,9 +401,9 @@ it.layer(NodeServices.layer)("providerMaintenance", (it) => {
399401 provider: driver("scopedPackageTool"),
400402 packageName: "@example/scoped-package-tool",
401403 update: {
402 command: "scoped-package-tool upgrade",
404 command: `${scopedPackageToolPath} upgrade`,
403405
404 executable: "scoped-package-tool",
406 executable: scopedPackageToolPath,
405407
406408 args: ["upgrade"],
407409
......@@ -411,6 +413,50 @@ it.layer(NodeServices.layer)("providerMaintenance", (it) => {
411413 }),
412414 );
413415
416 it.effect.skipIf(windowsHost)("runs an explicit native updater outside PATH", () =>
417 Effect.gen(function* () {
418 const fs = yield* FileSystem.FileSystem;
419 const tempDir = yield* fs.makeTempDirectoryScoped({ prefix: "t3-native-update-" });
420 const nativeBinDir = NodePath.join(tempDir, "with spaces", ".scoped-package-tool", "bin");
421 yield* fs.makeDirectory(nativeBinDir, { recursive: true });
422 const binaryPath = NodePath.join(nativeBinDir, "scoped-package-tool");
423 yield* fs.writeFileString(binaryPath, "#!/bin/sh\nprintf '%s' \"$1\"\n");
424 yield* fs.chmod(binaryPath, 0o755);
425
426 const capabilities = yield* resolveProviderMaintenanceCapabilitiesEffect(
427 scopedPackageToolUpdate,
428 { binaryPath, env: { PATH: "" } },
429 );
430 const update = capabilities.update;
431 expect(update).not.toBeNull();
432 if (!update) return;
433 const result = NodeChildProcess.spawnSync(update.executable, update.args, {
434 env: { PATH: "" },
435 encoding: "utf8",
436 });
437 expect(result.error).toBeUndefined();
438 expect(result.status).toBe(0);
439 expect(result.stdout).toBe("upgrade");
440 }).pipe(Effect.scoped),
441 );
442
443 it.each([
444 "/Users/example/.local/bin/native-package-tool",
445 "C:\\Users\\Example User\\.local\\bin\\native-package-tool.exe",
446 ])("preserves a configured native executable path: %s", (binaryPath) => {
447 expect(nativePackageToolUpdate.resolve({ binaryPath }).update?.executable).toBe(binaryPath);
448 });
449
450 it("uses the resolved launcher when its symlink target identifies a native install", () => {
451 const launcher = "/custom tools/native-launcher";
452 const capabilities = nativePackageToolUpdate.resolve({
453 binaryPath: launcher,
454 resolvedCommandPath: launcher,
455 realCommandPath: "/Users/example/.local/bin/native-package-tool",
456 });
457 expect(capabilities.update?.executable).toBe(launcher);
458 });
459
414460 it("switches native-package-tool to Homebrew updates when the binary resolves through Homebrew", () => {
415461 expect(
416462 nativePackageToolUpdate.resolve({
apps/server/src/provider/providerMaintenance.ts+3-2
......@@ -209,6 +209,7 @@ function makeHomebrewProviderMaintenanceCapabilities(
209209
210210function makeNativeProviderMaintenanceCapabilities(
211211 definition: PackageManagedProviderMaintenanceDefinition,
212 commandPath: string,
212213): ProviderMaintenanceCapabilities | null {
213214 if (!definition.nativeUpdate) {
214215 return null;
......@@ -217,7 +218,7 @@ function makeNativeProviderMaintenanceCapabilities(
217218 return makeProviderMaintenanceCapabilities({
218219 provider: definition.provider,
219220 packageName: definition.npmPackageName,
220 updateExecutable: definition.nativeUpdate.executable,
221 updateExecutable: commandPath,
221222 updateArgs: definition.nativeUpdate.args,
222223 updateLockKey: definition.nativeUpdate.lockKey,
223224 });
......@@ -297,7 +298,7 @@ export function resolvePackageManagedProviderMaintenance(
297298 commandPaths.some((commandPath) => nativeUpdate.isCommandPath(commandPath))
298299 ) {
299300 return (
300 makeNativeProviderMaintenanceCapabilities(definition) ??
301 makeNativeProviderMaintenanceCapabilities(definition, resolvedCommandPath) ??
301302 makeNpmGlobalProviderMaintenanceCapabilities(definition)
302303 );
303304 }