From 1f2a869e5badc1dfc9ef494f9fbb0926cd5cb5dc Mon Sep 17 00:00:00 2001 From: N0zoM1z0 <161784452+N0zoM1z0@users.noreply.github.com> Date: Wed, 11 Nov 2026 08:46:30 +0000 Subject: [PATCH] fix(reference): preserve directory intent through portable target rebasing Co-authored-by: gensokyo-dev <342580529+gensokyo-dev@users.noreply.github.com> --- .../ReferenceSourceImportEntries.test.ts | 24 +++++++++++++++++++ .../ReferenceSourceImportEntries.ts | 3 ++- src/domain/referenceSourcePathIdentity.ts | 9 +++++++ 3 files changed, 35 insertions(+), 1 deletion(-) diff --git a/src/application/ReferenceSourceImportEntries.test.ts b/src/application/ReferenceSourceImportEntries.test.ts index 70dab2e31c..3bd85788f6 100644 --- a/src/application/ReferenceSourceImportEntries.test.ts +++ b/src/application/ReferenceSourceImportEntries.test.ts @@ -640,3 +640,27 @@ it("prefers a later exact filename over an earlier unique canonical candidate", expect.objectContaining({ to: `${composed}.js`, resolution: "internal" }), ]); }); + +it("retains a missing target's directory requirement when rebasing from the inventory root", () => { + const read: ReferenceSourceRead = { + root: "/selected", + entries: [ + { + status: "read", + kind: "symlink", + path: "nested/link", + target: "sub/missing/", + targetState: "missing", + }, + ], + bytesRead: 0, + limitations: [], + }; + expect(parseReferenceSourceEntries(read, []).entries).toContainEqual( + expect.objectContaining({ + path: "nested/link", + target: "../sub/missing/", + target_state: "missing", + }), + ); +}); diff --git a/src/application/ReferenceSourceImportEntries.ts b/src/application/ReferenceSourceImportEntries.ts index 69462c09d2..ac05eb7e16 100644 --- a/src/application/ReferenceSourceImportEntries.ts +++ b/src/application/ReferenceSourceImportEntries.ts @@ -6,6 +6,7 @@ import type { JavaScriptPackageType } from "../domain/javascript/javascriptSourc import { logicalPathEscapesRoot } from "../domain/artifactIdentity.js"; import { createReferenceSourcePathLookup, + rebaseReferenceSourceTarget, decodeReferenceSourceModuleUrlPath, isPortableAbsoluteReferenceTarget, type ReferenceSourcePathLookup, @@ -630,7 +631,7 @@ const appendReadEntry = ( entry.targetState === "internal" || (entry.targetState === "missing" && !isPortableAbsoluteReferenceTarget(entry.target)) - ? posix.relative(posix.dirname(entry.path), entry.target) || "." + ? rebaseReferenceSourceTarget(entry.path, entry.target) : entry.target, target_state: entry.targetState, classifications, diff --git a/src/domain/referenceSourcePathIdentity.ts b/src/domain/referenceSourcePathIdentity.ts index 9a1718973d..d6473cff87 100644 --- a/src/domain/referenceSourcePathIdentity.ts +++ b/src/domain/referenceSourcePathIdentity.ts @@ -75,3 +75,12 @@ export const decodeReferenceSourceModuleUrlPath = ( ); } }; + +/** Rebase a portable target without discarding its directory-required suffix. */ +export const rebaseReferenceSourceTarget = ( + linkPath: string, + target: string, +): string => { + const relative = posix.relative(posix.dirname(linkPath), target) || "."; + return target.endsWith("/") ? `${relative}/` : relative; +};