From 7ffa4accc06869e8c7dac7c71d8fb472ee2225cf Mon Sep 17 00:00:00 2001 From: Hypit H1 Investigation Date: Wed, 16 Sep 2026 00:57:57 +0800 Subject: [PATCH] fix: resolve optional upstream assets from the machine npm fallback fonts-open declares 109 bundled @fontsource assets in optionalDependencies, but declaredExternalPackageRoot read only dependencies, so every documented 'hypit packages install @' repair left the asset unreachable. Read optionalDependencies as the fallback and cover it with a locator test. --- packages/package-loader-node/src/location.ts | 10 ++++-- .../package-loader-node/test/location.test.ts | 32 +++++++++++++++++++ 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/packages/package-loader-node/src/location.ts b/packages/package-loader-node/src/location.ts index da2f3abb..7bd49951 100644 --- a/packages/package-loader-node/src/location.ts +++ b/packages/package-loader-node/src/location.ts @@ -81,8 +81,14 @@ export function declaredExternalPackageRoot(root: string, from: string | URL, na while (true) { const manifest = join(cursor, "package.json"); if (existsSync(manifest)) { - const value = JSON.parse(readFileSync(manifest, "utf8")) as { dependencies?: Record }; - const version = value.dependencies?.[name]; + const value = JSON.parse(readFileSync(manifest, "utf8")) as { + dependencies?: Record; + optionalDependencies?: Record; + }; + // An upstream asset a package ships as optional is still selected by one exact version here. + // `hypit packages install` places it under the machine npm root named by this selection, so + // reading only the required map makes the documented repair unusable for every optional asset. + const version = value.dependencies?.[name] ?? value.optionalDependencies?.[name]; return version === undefined ? undefined : externalPackageInstallRoot(root, name, version); } const parent = dirname(cursor); diff --git a/packages/package-loader-node/test/location.test.ts b/packages/package-loader-node/test/location.test.ts index 6052fd5e..767dd682 100644 --- a/packages/package-loader-node/test/location.test.ts +++ b/packages/package-loader-node/test/location.test.ts @@ -120,3 +120,35 @@ test("package Source resolution reads one public export without activating packa await rm(project, { recursive: true, force: true }); } }); + +test("a machine npm fallback resolves an upstream asset the importer declares optionally", async () => { + const distribution = await mkdtemp(join(tmpdir(), "hypit-optional-distribution-")); + const machine = await mkdtemp(join(tmpdir(), "hypit-optional-machine-")); + try { + const asset = await machinePackage( + externalPackageInstallRoot(machine, "optional-only", "3.2.1"), "optional-only", + { version: "3.2.1" }, { "files/asset.css": "/* asset */\n" }); + const importer = join(distribution, "packages", "fonts", "surface.mjs"); + await mkdir(join(importer, ".."), { recursive: true }); + // `hypit packages install @` tells the author to install exactly this asset, so + // the version selection must read the same declaration the install was addressed to. + await writeFile(join(importer, "..", "package.json"), JSON.stringify({ + optionalDependencies: { "optional-only": "3.2.1" }, + })); + const options = { + from: importer, + distributionRoots: [distribution], + externalRoots: [machine], + } as const; + + const canonical = await realpath(asset); + assert.equal(locateNodePackage("optional-only", options).root, canonical); + assert.equal( + resolveNodePackageResource("optional-only", "files/asset.css", options), + join(canonical, "files", "asset.css"), + ); + } finally { + await rm(distribution, { recursive: true, force: true }); + await rm(machine, { recursive: true, force: true }); + } +});