Skip to content

Commit 0eba1c7

Browse files
backspaceclaude
andcommitted
Thread VN through serializers/code-ref.ts and delete the no-VN branch
`codeRefAdjustments` carried a no-VN branch reachable from the `deserializeAbsolute` path because the framework field-deserialize machinery called it without opts. Now `deserializeAbsolute` accepts the framework's store parameter and threads `store.virtualNetwork` into `codeRefAdjustments`, so the no-VN branch in the inner `resolve` helper is unreachable from any framework-driven path. Tighten `codeRefAdjustments` to require `opts.virtualNetwork` and drop the no-VN URL-join fallback. `serialize` also tightens its opts.virtualNetwork to required; the early-out when opts is missing preserves the public surface for direct callers that pass no opts. `deserializeAbsolute` has an early-out when no store is supplied — only direct test callers reach that path, and they get a passthrough. The code-ref-test.ts unit test that exercised the `serialize` no-VN path is updated to pass an empty `new VirtualNetwork()`; the assertion is unchanged because the URL-form base+ref still resolves the same way through VN. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1 parent 9cbe153 commit 0eba1c7

2 files changed

Lines changed: 34 additions & 33 deletions

File tree

packages/host/tests/unit/code-ref-test.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { module, test } from 'qunit';
66

77
import type { Loader, LooseCardResource } from '@cardstack/runtime-common';
88
import {
9+
VirtualNetwork,
910
baseRealm,
1011
loadCardDef,
1112
rri,
@@ -194,6 +195,7 @@ module('code-ref', function (hooks) {
194195
let doc = { data: { id: base.href } };
195196
let serialized = CodeRefSerializer.serialize(ref, doc, undefined, {
196197
relativeTo: base,
198+
virtualNetwork: new VirtualNetwork(),
197199
}) as any;
198200
assert.strictEqual(
199201
serialized.module,

packages/runtime-common/serializers/code-ref.ts

Lines changed: 32 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type {
22
BaseDefConstructor,
33
BaseInstanceType,
4+
CardStore,
45
} from 'https://cardstack.com/base/card-api';
56
import {
67
type ResolvedCodeRef,
@@ -31,17 +32,20 @@ export function serialize(
3132
trimExecutableExtension?: true;
3233
maybeRelativeReference?: (reference: string) => string;
3334
allowRelative?: true;
34-
virtualNetwork?: VirtualNetwork;
35+
virtualNetwork: VirtualNetwork;
3536
},
3637
): ResolvedCodeRef | {} {
37-
let vn = opts?.virtualNetwork;
38+
if (!opts) {
39+
return { ...codeRef };
40+
}
41+
let vn = opts.virtualNetwork;
3842
let baseURL: URL | undefined;
39-
if (opts?.relativeTo instanceof URL) {
43+
if (opts.relativeTo instanceof URL) {
4044
baseURL = opts.relativeTo;
41-
} else if (typeof opts?.relativeTo === 'string') {
42-
baseURL = vn ? vn.toURL(opts.relativeTo) : undefined;
45+
} else if (typeof opts.relativeTo === 'string') {
46+
baseURL = vn.toURL(opts.relativeTo);
4347
} else if (doc?.data?.id && typeof doc.data.id === 'string') {
44-
baseURL = vn ? vn.toURL(doc.data.id) : undefined;
48+
baseURL = vn.toURL(doc.data.id);
4549
}
4650
return {
4751
...codeRef,
@@ -65,21 +69,33 @@ export async function deserializeAbsolute<T extends BaseDefConstructor>(
6569
this: T,
6670
codeRef: ResolvedCodeRef | {},
6771
relativeTo: RealmResourceIdentifier | URL | undefined,
72+
_doc?: unknown,
73+
store?: CardStore,
6874
): Promise<BaseInstanceType<T>> {
75+
if (!store) {
76+
// Reached only by direct test callers that bypass the framework
77+
// protocol; the framework's field-deserialize path always supplies
78+
// a store. Without a VN we can't resolve prefix-form refs or
79+
// round-trip URL-form refs through registered mappings, so leave
80+
// the codeRef untouched.
81+
return { ...codeRef } as BaseInstanceType<T>;
82+
}
6983
return {
7084
...codeRef,
71-
...codeRefAdjustments(codeRef, relativeTo),
85+
...codeRefAdjustments(codeRef, relativeTo, {
86+
virtualNetwork: store.virtualNetwork,
87+
}),
7288
} as BaseInstanceType<T>;
7389
}
7490

7591
function codeRefAdjustments(
7692
codeRef: any,
77-
relativeTo?: RealmResourceIdentifier | URL,
78-
opts?: Omit<SerializeOpts, 'virtualNetwork'> & {
93+
relativeTo: RealmResourceIdentifier | URL | undefined,
94+
opts: Omit<SerializeOpts, 'virtualNetwork'> & {
7995
trimExecutableExtension?: true;
8096
maybeRelativeReference?: (reference: string) => string;
8197
allowRelative?: true;
82-
virtualNetwork?: VirtualNetwork;
98+
virtualNetwork: VirtualNetwork;
8399
},
84100
) {
85101
if (!codeRef) {
@@ -88,35 +104,18 @@ function codeRefAdjustments(
88104
if (!isResolvedCodeRef(codeRef)) {
89105
return {};
90106
}
91-
// The `deserializeAbsolute` field-deserialize path reaches this without
92-
// opts (no VN, no `allowRelative`, no `maybeRelativeReference`). For
93-
// URL-like refs we can still do a plain URL-join against `relativeTo`
94-
// and apply `trimExecutableExtension`. Bare specifiers (e.g.
95-
// `@cardstack/boxel-host/…`) throw — `resolve` is wrapped in try/catch
96-
// below, so the original ref stays intact for the loader's importMap
97-
// shim.
98-
let vn = opts?.virtualNetwork;
99-
let resolve = (ref: string) => {
100-
if (vn) {
101-
return resolveModuleHref(ref, relativeTo, vn);
102-
}
103-
if (!isUrlLike(ref)) {
104-
throw new Error(
105-
`Cannot resolve bare package specifier "${ref}" — no matching prefix mapping registered`,
106-
);
107-
}
108-
return new URL(ref, relativeTo).href;
109-
};
107+
let vn = opts.virtualNetwork;
108+
let resolve = (ref: string) => resolveModuleHref(ref, relativeTo, vn);
110109
if (!isUrlLike(codeRef.module)) {
111110
// Try resolving via registered prefix mappings (e.g., @cardstack/catalog/)
112111
try {
113112
let resolved = resolve(codeRef.module);
114113
if (resolved !== codeRef.module) {
115114
let module: string = resolved;
116-
if (opts?.trimExecutableExtension) {
115+
if (opts.trimExecutableExtension) {
117116
module = trimExecutableExtension(rri(module));
118117
}
119-
if (opts?.allowRelative && opts?.maybeRelativeReference) {
118+
if (opts.allowRelative && opts.maybeRelativeReference) {
120119
module = opts.maybeRelativeReference(module);
121120
}
122121
return { module };
@@ -128,10 +127,10 @@ function codeRefAdjustments(
128127
}
129128
if (relativeTo) {
130129
let module: string = resolve(codeRef.module);
131-
if (opts?.trimExecutableExtension) {
130+
if (opts.trimExecutableExtension) {
132131
module = trimExecutableExtension(rri(module));
133132
}
134-
if (opts?.allowRelative && opts?.maybeRelativeReference) {
133+
if (opts.allowRelative && opts.maybeRelativeReference) {
135134
module = opts.maybeRelativeReference(module);
136135
}
137136
return { module };

0 commit comments

Comments
 (0)