diff --git a/packages/provider-core/src/server/maintenanceResolver.test.ts b/packages/provider-core/src/server/maintenanceResolver.test.ts index cf6527114da67..9447742ca354b 100644 --- a/packages/provider-core/src/server/maintenanceResolver.test.ts +++ b/packages/provider-core/src/server/maintenanceResolver.test.ts @@ -1132,6 +1132,34 @@ it.layer( }, ); + it.effect.skipIf(!symlinksSupported || windowsHost)( + "updates an npm-owned launcher in a native install location with npm", + () => + Effect.gen(function* () { + const tempDir = yield* makeTempDir("t3-native-npm"); + const prefix = NodePath.join(tempDir, ".local"); + const packageBin = NodePath.join("node_modules", "@example", "native-package-tool", "bin"); + writeExecutable(NodePath.join(prefix, "lib", packageBin, "native-package-tool.exe")); + const launcher = NodePath.join(prefix, "bin", "native-package-tool"); + NodeFS.mkdirSync(NodePath.dirname(launcher), { recursive: true }); + NodeFS.symlinkSync( + NodePath.join("..", "lib", packageBin, "native-package-tool.exe"), + launcher, + ); + + const capabilities = yield* resolveProviderMaintenanceCapabilitiesEffect( + nativePackageToolUpdate, + { binaryPath: launcher, env: { PATH: "" } }, + ).pipe(Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, noSpawn)); + + expect(capabilities.update).toMatchObject({ + executable: "npm", + args: expect.arrayContaining(["--prefix", prefix]), + lockKey: `npm-global:${normalizeCommandPath(prefix)}`, + }); + }), + ); + it.effect.skipIf(!symlinksSupported)( "prefers npm ownership over the Node keg the package lives under", () => diff --git a/packages/provider-core/src/server/maintenanceResolver.ts b/packages/provider-core/src/server/maintenanceResolver.ts index 5f5efaae13c80..364b4cee9bf42 100644 --- a/packages/provider-core/src/server/maintenanceResolver.ts +++ b/packages/provider-core/src/server/maintenanceResolver.ts @@ -532,10 +532,11 @@ export const resolvePackageManagedProviderMaintenance = Effect.fn( : {}), }) : manual; - // A native-looking launcher can still delegate to a mise-owned install. + // A native-looking launcher can still delegate to an npm- or mise-owned install. if ( nativeUpdate?.isCommandPath && commandPaths.some(nativeUpdate.isCommandPath) && + !npmGlobalPrefixFromCommandPath(context.realCommandPath, packageName) && !commandPaths.some(isMiseCommandPath) && !(yield* isMiseWrapperScript(context)) ) {