Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 26 additions & 1 deletion packages/host/tests/unit/loader-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@ import { getService } from '@universal-ember/test-support';

import { module, test } from 'qunit';

import { baseRealm, Loader } from '@cardstack/runtime-common';
import {
baseRealm,
Loader,
registerCardReferencePrefix,
} from '@cardstack/runtime-common';

import {
testRealmURL,
Expand Down Expand Up @@ -229,6 +233,27 @@ module('Unit | loader', function (hooks) {
assert.strictEqual(myLoader(), loader, 'the loader instance is correct');
});

// Regression test for CS-10498: after the import-maps change, module
// identifiers can be in registered prefix form (e.g. @cardstack/catalog/...).
// getConsumedModules passed these directly to new URL() which throws
// TypeError: Invalid URL. The fix uses resolveCardReference() first.
test('can determine consumed modules using prefix-form module identifier', async function (assert) {
registerCardReferencePrefix('@test-loader/', testRealmURL);

// Import the module using its regular URL so it's in the loader cache
await loader.import(`${testRealmURL}f`);

// Now call getConsumedModules with the prefix-form identifier.
// Without the fix, this throws TypeError: Invalid URL because
// new URL('@test-loader/f') is not a valid URL.
let consumed = await loader.getConsumedModules(`@test-loader/f`);
assert.deepEqual(
consumed,
[`${testRealmURL}b`, `${testRealmURL}c`, `${testRealmURL}g`],
'consumed modules resolved correctly from prefix-form identifier',
);
});

test('identify preserves original module for reexports', function (assert) {
let throwIfFetch = new Loader(async () => {
throw new Error(
Expand Down
24 changes: 18 additions & 6 deletions packages/runtime-common/loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@ import {
trackRuntimeModuleDependency,
type RuntimeDependencyTrackingContext,
} from './dependency-tracker';
import { unresolveCardReference } from './card-reference-resolver';
import {
unresolveCardReference,
resolveCardReference,
} from './card-reference-resolver';

type FetchingModule = {
state: 'fetching';
Expand Down Expand Up @@ -158,16 +161,25 @@ export class Loader {
consumed: string[] = [],
initialIdentifier = moduleIdentifier,
): Promise<string[]> {
if (consumed.includes(moduleIdentifier)) {
// Normalize to resolved URL href so that prefix-form identifiers
// (e.g. @cardstack/catalog/...) and their resolved URL equivalents
// are treated as the same module for cycle detection and self-exclusion.
let resolvedHref = new URL(
resolveCardReference(moduleIdentifier, undefined),
).href;
let resolvedInitial = new URL(
resolveCardReference(initialIdentifier, undefined),
).href;

if (consumed.includes(resolvedHref)) {
return [];
}
// you can't consume yourself
if (moduleIdentifier !== initialIdentifier) {
consumed.push(moduleIdentifier);
if (resolvedHref !== resolvedInitial) {
consumed.push(resolvedHref);
}

let resolvedModuleIdentifier = new URL(moduleIdentifier);
let module = this.getModule(resolvedModuleIdentifier.href);
let module = this.getModule(resolvedHref);

if (!module || module.state === 'fetching') {
// we haven't yet tried importing the module or we are still in the process of importing the module
Expand Down
Loading