Skip to content
Closed
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
9 changes: 7 additions & 2 deletions packages/host/app/services/network.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,10 +61,15 @@ export default class NetworkService extends Service {
});
if (config.resolvedCatalogRealmURL) {
let catalogURL = withTrailingSlash(config.resolvedCatalogRealmURL);
registerCardReferencePrefix('@cardstack/catalog/', catalogURL);
let catalogSchemeURL = 'cardstack://catalog/';
registerCardReferencePrefix('@cardstack/catalog/', catalogSchemeURL);
virtualNetwork.addURLMapping(
new URL(catalogSchemeURL),
new URL(catalogURL),
);
virtualNetwork.addImportMap(
'@cardstack/catalog/',
(rest) => new URL(rest, catalogURL).href,
(rest) => new URL(rest, catalogSchemeURL).href,
);
}
return virtualNetwork;
Expand Down
17 changes: 17 additions & 0 deletions packages/realm-server/handlers/handle-prerender-proxy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type Koa from 'koa';
import {
fetchRealmPermissions,
type DBAdapter,
type Realm,
type RealmPermissions,
type Prerenderer,
} from '@cardstack/runtime-common';
Expand All @@ -21,11 +22,13 @@ export default function handlePrerenderProxy({
kind,
prerenderer,
dbAdapter,
realms,
createPrerenderAuth,
}: {
kind: 'card' | 'module' | 'file-extract';
prerenderer?: Prerenderer;
dbAdapter: DBAdapter;
realms: Realm[];
createPrerenderAuth: (
userId: string,
permissions: RealmPermissions,
Expand Down Expand Up @@ -93,9 +96,23 @@ export default function handlePrerenderProxy({
return;
}

// Include permissions for all known realms so cross-realm
// dependencies (e.g. base realm modules) can be fetched during prerender
let permissions: RealmPermissions = {
[attrs.realm]: userPermissions,
};
for (let realm of realms) {
if (realm.url !== attrs.realm) {
let realmPerms = await fetchRealmPermissions(
dbAdapter,
new URL(realm.url),
);
let perms = realmPerms[token.user];
if (perms?.length) {
permissions[realm.url] = perms;
}
}
}

let auth = createPrerenderAuth(token.user, permissions);

Expand Down
14 changes: 10 additions & 4 deletions packages/realm-server/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -227,10 +227,16 @@ for (let i = 0; i < fromUrls.length; i++) {
virtualNetwork.addURLMapping(fromURL, to);
urlMappings.push([fromURL, to]);
} else {
// Non-URL prefix like @cardstack/catalog/
registerCardReferencePrefix(from, to.href);
virtualNetwork.addImportMap(from, (rest) => new URL(rest, to).href);
urlMappings.push([to, to]); // use toUrl for both in hrefs
// Non-URL prefix like @cardstack/catalog/ → cardstack://catalog/
let realmName = from.replace(/^@cardstack\//, '').replace(/\/$/, '');
let schemeURL = new URL(`cardstack://${realmName}/`);
registerCardReferencePrefix(from, schemeURL.href);
virtualNetwork.addURLMapping(schemeURL, to);
virtualNetwork.addImportMap(
from,
(rest) => new URL(rest, schemeURL).href,
);
urlMappings.push([schemeURL, to]);
}
}
let hrefs = urlMappings.map(([from, to]) => [from.href, to.href]);
Expand Down
3 changes: 3 additions & 0 deletions packages/realm-server/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@ export function createRoutes(args: CreateRoutesArgs) {
kind: 'card',
prerenderer: args.prerenderer,
dbAdapter: args.dbAdapter,
realms: args.realms,
createPrerenderAuth,
}),
);
Expand All @@ -187,6 +188,7 @@ export function createRoutes(args: CreateRoutesArgs) {
kind: 'module',
prerenderer: args.prerenderer,
dbAdapter: args.dbAdapter,
realms: args.realms,
createPrerenderAuth,
}),
);
Expand All @@ -197,6 +199,7 @@ export function createRoutes(args: CreateRoutesArgs) {
kind: 'file-extract',
prerenderer: args.prerenderer,
dbAdapter: args.dbAdapter,
realms: args.realms,
createPrerenderAuth,
}),
);
Expand Down
39 changes: 25 additions & 14 deletions packages/realm-server/scripts/migrate-realm-references.sh
Original file line number Diff line number Diff line change
Expand Up @@ -25,21 +25,23 @@
# staging -> https://realms-staging.stack.cards/
# production -> https://app.boxel.ai/
#
# The replacement is auto-derived as @cardstack/<realm>/
# The replacement is cardstack://<realm>/
#
# Examples:
# # Shortcut form
# # Shortcut form — convert environment URLs to cardstack:// scheme
# ./migrate-realm-references.sh --dry-run -e staging -r catalog /persistent/catalog /persistent/experiments
#
# # Equivalent explicit form
# ./migrate-realm-references.sh --dry-run https://realms-staging.stack.cards/catalog/ @cardstack/catalog/ /persistent/catalog /persistent/experiments
# # Explicit: convert an environment URL to cardstack://
# ./migrate-realm-references.sh https://realms-staging.stack.cards/catalog/ cardstack://catalog/ /persistent/catalog
#
# # More shortcut examples
# ./migrate-realm-references.sh -e production -r base /persistent/base /persistent/catalog /persistent/experiments
# ./migrate-realm-references.sh -e development -r skills ./realms/
# # Convert legacy @cardstack/ prefix to cardstack:// scheme
# ./migrate-realm-references.sh @cardstack/catalog/ cardstack://catalog/ ./realms/
#
# # Reverse (prefix -> URL, explicit form only)
# ./migrate-realm-references.sh @cardstack/base/ https://cardstack.com/base/ ./realms/
# # Convert https://cardstack.com/base/ to cardstack:// scheme
# ./migrate-realm-references.sh https://cardstack.com/base/ cardstack://base/ ./realms/
#
# # Reverse (cardstack:// -> URL, explicit form only)
# ./migrate-realm-references.sh cardstack://base/ https://cardstack.com/base/ ./realms/
#
# To roll back:
# patch -R -p0 < <name>.patch
Expand Down Expand Up @@ -100,7 +102,7 @@ if [ -n "$ENV" ] || [ -n "$REALM" ]; then
esac

FIND_STR="${BASE_URL}${REALM}/"
REPLACEMENT="@cardstack/${REALM}/"
REPLACEMENT="cardstack://${REALM}/"

echo "Resolved: $FIND_STR -> $REPLACEMENT"

Expand All @@ -125,6 +127,16 @@ else
echo " development -> http://localhost:4201/"
echo " staging -> https://realms-staging.stack.cards/"
echo " production -> https://app.boxel.ai/"
echo ""
echo "Examples:"
echo " # Environment URLs to cardstack:// scheme"
echo " $0 -e staging -r catalog /persistent/catalog"
echo ""
echo " # Legacy @cardstack/ prefix to cardstack:// scheme"
echo " $0 @cardstack/catalog/ cardstack://catalog/ ./realms/"
echo ""
echo " # https://cardstack.com/base/ to cardstack:// scheme"
echo " $0 https://cardstack.com/base/ cardstack://base/ ./realms/"
exit 1
fi

Expand All @@ -137,9 +149,8 @@ fi
FIND_STR="${FIND_STR%/}/"
REPLACEMENT="${REPLACEMENT%/}/"

# If <find> is a URL, extract the path portion for matching path-only references.
# e.g., https://realms-staging.stack.cards/catalog/ -> /catalog/
# For non-URL find strings (like @cardstack/base/), skip path-only matching.
# Determine the type of find string for matching strategy.
# URLs get path-only matching; other strings (like @cardstack/) get literal-only.
IS_URL=false
REALM_PATH=""
if echo "$FIND_STR" | grep -qE '^https?://'; then
Expand All @@ -149,7 +160,7 @@ fi

# Derive patch filename from the find/replace strings
# e.g. @cardstack/catalog/ -> catalog, https://cardstack.com/base/ -> base
PATCH_NAME=$(echo "$FIND_STR $REPLACEMENT" | sed -E 's|https?://[^/]*/||g; s|@cardstack/||g; s|/||g; s| |-to-|')
PATCH_NAME=$(echo "$FIND_STR $REPLACEMENT" | sed -E 's|https?://[^/]*/||g; s|cardstack://||g; s|@cardstack/||g; s|/||g; s| |-to-|')
PATCH_FILE="${PATCH_NAME}.patch"

total_files=0
Expand Down
16 changes: 10 additions & 6 deletions packages/realm-server/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1101,14 +1101,18 @@ export class RealmServer {

function detectRealmCollision(realms: Realm[]): void {
let collisions: string[] = [];
let realmsURLs = realms.map(({ url }) => ({
url,
path: new URL(url).pathname,
}));
let realmsURLs = realms.map(({ url }) => {
let parsed = new URL(url);
return {
url,
// Use origin+pathname so different schemes/hosts never collide (http:// vs cardstack://)
fullPath: parsed.origin + parsed.pathname,
};
});
for (let realmA of realmsURLs) {
for (let realmB of realmsURLs) {
if (realmA.path.length > realmB.path.length) {
if (realmA.path.startsWith(realmB.path)) {
if (realmA.fullPath.length > realmB.fullPath.length) {
if (realmA.fullPath.startsWith(realmB.fullPath)) {
collisions.push(`${realmA.url} collides with ${realmB.url}`);
}
}
Expand Down
6 changes: 6 additions & 0 deletions packages/realm-server/tests/prerender-proxy-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ module(basename(__filename), function () {
kind: 'card',
prerenderer,
dbAdapter,
realms: [],
createPrerenderAuth,
}),
);
Expand Down Expand Up @@ -218,6 +219,7 @@ module(basename(__filename), function () {
kind: 'card',
prerenderer: undefined,
dbAdapter: makeDbAdapter([]),
realms: [],
createPrerenderAuth,
}),
);
Expand Down Expand Up @@ -251,6 +253,7 @@ module(basename(__filename), function () {
kind: 'card',
prerenderer,
dbAdapter: makeDbAdapter([]),
realms: [],
createPrerenderAuth,
}),
);
Expand Down Expand Up @@ -286,6 +289,7 @@ module(basename(__filename), function () {
kind: 'card',
prerenderer,
dbAdapter: makeDbAdapter([]), // no permissions
realms: [],
createPrerenderAuth,
}),
);
Expand Down Expand Up @@ -336,6 +340,7 @@ module(basename(__filename), function () {
kind: 'card',
prerenderer,
dbAdapter,
realms: [],
createPrerenderAuth,
}),
);
Expand All @@ -346,6 +351,7 @@ module(basename(__filename), function () {
kind: 'module',
prerenderer,
dbAdapter,
realms: [],
createPrerenderAuth,
}),
);
Expand Down
11 changes: 9 additions & 2 deletions packages/realm-server/worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,8 +106,15 @@ for (let i = 0; i < fromUrls.length; i++) {
if (isUrlLike(from)) {
virtualNetwork.addURLMapping(new URL(from), to);
} else {
registerCardReferencePrefix(from, to.href);
virtualNetwork.addImportMap(from, (rest) => new URL(rest, to).href);
// Non-URL prefix like @cardstack/catalog/ → cardstack://catalog/
let realmName = from.replace(/^@cardstack\//, '').replace(/\/$/, '');
let schemeURL = new URL(`cardstack://${realmName}/`);
registerCardReferencePrefix(from, schemeURL.href);
virtualNetwork.addURLMapping(schemeURL, to);
virtualNetwork.addImportMap(
from,
(rest) => new URL(rest, schemeURL).href,
);
}
}
let autoMigrate = migrateDB || undefined;
Expand Down
3 changes: 2 additions & 1 deletion packages/runtime-common/card-reference-resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ function isUrlLikeReference(ref: string): boolean {
ref.startsWith('.') ||
ref.startsWith('/') ||
ref.startsWith('http://') ||
ref.startsWith('https://')
ref.startsWith('https://') ||
ref.startsWith('cardstack://')
);
}

Expand Down
3 changes: 2 additions & 1 deletion packages/runtime-common/virtual-network.ts
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,8 @@ export function isUrlLike(moduleIdentifier: string): boolean {
moduleIdentifier.startsWith('.') ||
moduleIdentifier.startsWith('/') ||
moduleIdentifier.startsWith('http://') ||
moduleIdentifier.startsWith('https://')
moduleIdentifier.startsWith('https://') ||
moduleIdentifier.startsWith('cardstack://')
);
}

Expand Down
Loading