Environment
|
|
| Hypit |
@hypit/hypit 0.1.8 (npm install); also present on current main = v0.1.9 (2f2bb329) |
| Install method |
npm install @hypit/hypit (not a source checkout) |
| OS |
Windows 11 (10.0.26200.9445) |
| Node |
v22.22.2 |
| npm |
10.9.7 |
| Machine package root |
HYPIT_STATE_HOME is set; machine packages live under <HYPIT_STATE_HOME>/packages |
Reproduction
Author a minimal local Source that uses one of the built-in fonts (no Provider, no network), then:
# 1. check
$ hypit check productions/hello-world/chat.svml
x Source is invalid
@fontsource-variable/inter is needed by this authored font. Install it once with:
hypit packages install @fontsource-variable/inter@5.3.0
# 2. do exactly what it says
$ hypit packages install @fontsource-variable/inter@5.3.0
$ hypit packages status @fontsource-variable/inter@5.3.0
... Ready true # the CLI agrees the package is on disk
# 3. check again -- same error
$ hypit check productions/hello-world/chat.svml
@fontsource-variable/inter is needed by this authored font. Install it once with:
hypit packages install @fontsource-variable/inter@5.3.0
The package really is in the right place:
EXISTS <HYPIT_STATE_HOME>/packages/@fontsource-variable/inter/5.3.0/node_modules/@fontsource-variable/inter/package.json
So the CLI's own repair instruction can never fix it: install → reported Ready → same error.
Expected
After hypit packages install @fontsource-variable/inter@5.3.0, hypit check passes.
Actual
hypit check keeps reporting the same error, forever. All 109 bundled fonts
(69 × @fontsource-variable/* + 39 × @fontsource/* + @infolektuell/noto-color-emoji)
are unreachable this way.
Root cause
packages/package-loader-node/src/location.ts:
79 export function declaredExternalPackageRoot(root: string, from: string | URL, name: string): string | undefined {
80 let cursor = dirname(fromPath(from));
81 while (true) {
82 const manifest = join(cursor, "package.json");
83 if (existsSync(manifest)) {
84 const value = JSON.parse(readFileSync(manifest, "utf8")) as { dependencies?: Record<string, string> };
85 const version = value.dependencies?.[name]; // <== dependencies only
86 return version === undefined ? undefined : externalPackageInstallRoot(root, name, version);
87 }
88 const parent = dirname(cursor);
89 if (parent === cursor) return undefined;
90 cursor = parent;
91 }
92 }
packages/fonts-open/package.json declares all 109 fonts under optionalDependencies —
dependencies is empty. So declaredExternalPackageRoot always returns undefined.
Call chain:
hypit check <source.svml>
└─ fonts-open/src/surface.ts:26
resolveNodePackageResource(packageName, path, { from: import.meta.url })
└─ location.ts:243 resolveNodePackageResource
└─ location.ts:188 locateNodePackage
├─ L194 ancestorPackage(...) # 1. plain node_modules walk upward
├─ L206 nearby !== undefined → return # source checkout returns here
└─ L211-219 allowExternal branch
└─ L215 declaredExternalPackageRoot(...) # 2. machine-npm version pick
└─ L85 reads dependencies only → undefined
└─ L220 throw new Error(`cannot locate installed package ${name}`)
└─ fonts-open/src/surface.ts:30 rewraps as "… is needed by this authored font …"
Measured on both layouts (calling the exported functions directly):
| Resolution start point |
declaredExternalPackageRoot(root, from, "@fontsource-variable/inter") |
locateNodePackage(...) |
npm distribution .../node_modules/@hypit/hypit/packages/fonts-open/src/surface.ts |
undefined ← bug |
FAILED: cannot locate installed package @fontsource-variable/inter |
source checkout <repo>/packages/fonts-open/src/surface.ts |
undefined (same bug, masked) |
LOCATED <repo>/node_modules/.pnpm/@fontsource-variable+inter@5.3.0/… |
Why the source checkout is unaffected: pnpm install symlinks optionalDependencies into
packages/fonts-open/node_modules/ (69 @fontsource-variable/* symlinks measured), so
ancestorPackage hits at L206 and never reaches the broken L215 branch. In the npm
distribution there is no node_modules under the embedded package directory at all
(53 packages installed there, zero fonts), so L215 is the only path left.
Suggested minimal fix
Touch only L84-85:
- const value = JSON.parse(readFileSync(manifest, "utf8")) as { dependencies?: Record<string, string> };
- const version = value.dependencies?.[name];
+ const value = JSON.parse(readFileSync(manifest, "utf8")) as {
+ dependencies?: Record<string, string>;
+ optionalDependencies?: Record<string, string>;
+ };
+ const version = value.dependencies?.[name] ?? value.optionalDependencies?.[name];
Measured before/after on this machine:
before: declaredExternalPackageRoot(...) -> undefined
locateNodePackage(...) -> FAILED: cannot locate installed package
after: declaredExternalPackageRoot(...) -> <HYPIT_STATE_HOME>/packages/@fontsource-variable/inter/5.3.0
locateNodePackage(...) -> LOCATED <HYPIT_STATE_HOME>/packages/@fontsource-variable/inter/5.3.0/node_modules/@fontsource-variable/inter
Regression test
packages/package-loader-node/test/location.test.ts currently has zero coverage for
optionalDependencies. A case like this one should be added:
test("a machine npm fallback resolves an upstream asset the importer declares optionally", async () => {
// importer manifest declares optionalDependencies: { "optional-only": "3.2.1" }
// assert both locateNodePackage(...) and resolveNodePackageResource(...) hit the machine npm root
});
Measured: not ok before the fix, ok after.
Impact
Any optional upstream asset resolved through the machine npm root is affected. Today
fonts-open is the only package declaring upstream assets via optionalDependencies, so the
blast radius is "every fully-local render that uses a built-in font" — which is exactly the
$0 path the docs recommend for getting started.
Notes
- All evidence comes from read-only investigation plus direct calls to exported functions; no
installed files were modified.
- This is independent of the separate issue about npm-distribution local rendering failing
with Cannot find package '@hypit/hyperframes'. Fixing either one does not fix the other.
Environment
@hypit/hypit0.1.8 (npm install); also present on currentmain= v0.1.9 (2f2bb329)npm install @hypit/hypit(not a source checkout)HYPIT_STATE_HOMEis set; machine packages live under<HYPIT_STATE_HOME>/packagesReproduction
Author a minimal local Source that uses one of the built-in fonts (no Provider, no network), then:
The package really is in the right place:
So the CLI's own repair instruction can never fix it: install → reported Ready → same error.
Expected
After
hypit packages install @fontsource-variable/inter@5.3.0,hypit checkpasses.Actual
hypit checkkeeps reporting the same error, forever. All 109 bundled fonts(69 ×
@fontsource-variable/*+ 39 ×@fontsource/*+@infolektuell/noto-color-emoji)are unreachable this way.
Root cause
packages/package-loader-node/src/location.ts:packages/fonts-open/package.jsondeclares all 109 fonts underoptionalDependencies—dependenciesis empty. SodeclaredExternalPackageRootalways returnsundefined.Call chain:
Measured on both layouts (calling the exported functions directly):
declaredExternalPackageRoot(root, from, "@fontsource-variable/inter")locateNodePackage(...).../node_modules/@hypit/hypit/packages/fonts-open/src/surface.tsundefined← bugFAILED: cannot locate installed package @fontsource-variable/inter<repo>/packages/fonts-open/src/surface.tsundefined(same bug, masked)LOCATED <repo>/node_modules/.pnpm/@fontsource-variable+inter@5.3.0/…Why the source checkout is unaffected:
pnpm installsymlinksoptionalDependenciesintopackages/fonts-open/node_modules/(69@fontsource-variable/*symlinks measured), soancestorPackagehits at L206 and never reaches the broken L215 branch. In the npmdistribution there is no
node_modulesunder the embedded package directory at all(53 packages installed there, zero fonts), so L215 is the only path left.
Suggested minimal fix
Touch only L84-85:
Measured before/after on this machine:
Regression test
packages/package-loader-node/test/location.test.tscurrently has zero coverage foroptionalDependencies. A case like this one should be added:Measured:
not okbefore the fix,okafter.Impact
Any optional upstream asset resolved through the machine npm root is affected. Today
fonts-openis the only package declaring upstream assets viaoptionalDependencies, so theblast radius is "every fully-local render that uses a built-in font" — which is exactly the
$0 path the docs recommend for getting started.
Notes
installed files were modified.
with
Cannot find package '@hypit/hyperframes'. Fixing either one does not fix the other.