Skip to content
Open
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
5 changes: 5 additions & 0 deletions .changeset/warm-points-visit.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"bejamas": patch
---

Fix `bejamas add` registry path resolution so style-scoped installs use the active Bejamas style and work with shadcn's style-specific registry requests.
54 changes: 54 additions & 0 deletions apps/web/src/pages/r/[style]/colors/[name].json.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import fs from "node:fs/promises";
import path from "node:path";
import { BASE_COLORS, STYLES } from "@bejamas/create-config/server";
import { jsonResponse } from "@/utils/create-registry";
import { STATIC_ASSET_CACHE_CONTROL } from "@/utils/http-cache";

export const prerender = true;

const staticColorsRoot = path.resolve(process.cwd(), "public/r/colors");

export function getStaticPaths() {
return STYLES.flatMap((style) =>
BASE_COLORS.map((color) => ({
params: {
style: style.id,
name: color.name,
},
})),
);
}

export async function GET({
params,
}: {
params: { style: string; name: string };
}) {
const style = STYLES.find((entry) => entry.id === params.style);
if (!style) {
return jsonResponse(
{ error: "Style not found." },
{
status: 404,
headers: { "Cache-Control": STATIC_ASSET_CACHE_CONTROL },
},
);
}

const filepath = path.resolve(staticColorsRoot, `${params.name}.json`);

try {
const payload = JSON.parse(await fs.readFile(filepath, "utf8"));
return jsonResponse(payload, {
headers: { "Cache-Control": STATIC_ASSET_CACHE_CONTROL },
});
} catch {
return jsonResponse(
{ error: "Base color not found." },
{
status: 404,
headers: { "Cache-Control": STATIC_ASSET_CACHE_CONTROL },
},
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import fs from "node:fs/promises";
import path from "node:path";
import {
buildFontRegistryItem,
buildUtilsRegistryItem,
STYLES,
} from "@bejamas/create-config/server";
import { jsonResponse } from "@/utils/create-registry";
import { STATIC_ASSET_CACHE_CONTROL } from "@/utils/http-cache";
import { REQUESTED_STYLE_IDS } from "@/utils/registry-style-compat";

export const prerender = true;

const staticStylesRoot = path.resolve(process.cwd(), "public/r/styles");
const staticRegistryRoot = path.resolve(process.cwd(), "public/r");
const requestedStyleIds = REQUESTED_STYLE_IDS;

export async function getStaticPaths() {
const paths = await Promise.all(
STYLES.map(async (style) => {
const filenames = await fs.readdir(path.join(staticStylesRoot, style.id));
const names = filenames
.filter((filename) => filename.endsWith(".json"))
.map((filename) => filename.slice(0, -".json".length));

return requestedStyleIds.flatMap((requestedStyle) =>
names.map((name) => ({
params: {
style: style.id,
requestedStyle,
name,
},
})),
);
}),
);

return paths.flat();
}

export async function GET({
params,
}: {
params: { style: string; requestedStyle: string; name: string };
}) {
const style = STYLES.find((entry) => entry.id === params.style);
if (!style) {
return jsonResponse(
{ error: "Style not found." },
{
status: 404,
headers: { "Cache-Control": STATIC_ASSET_CACHE_CONTROL },
},
);
}

try {
const filepath = path.resolve(staticStylesRoot, style.id, `${params.name}.json`);
const item = JSON.parse(await fs.readFile(filepath, "utf8"));
return jsonResponse(item, {
headers: { "Cache-Control": STATIC_ASSET_CACHE_CONTROL },
});
} catch {}

if (params.name === "utils") {
return jsonResponse(buildUtilsRegistryItem(), {
headers: { "Cache-Control": STATIC_ASSET_CACHE_CONTROL },
});
}

const fontItem = buildFontRegistryItem(params.name);
if (fontItem) {
return jsonResponse(fontItem, {
headers: { "Cache-Control": STATIC_ASSET_CACHE_CONTROL },
});
}

try {
const filepath = path.resolve(staticRegistryRoot, `${params.name}.json`);
const item = JSON.parse(await fs.readFile(filepath, "utf8"));
return jsonResponse(item, {
headers: { "Cache-Control": STATIC_ASSET_CACHE_CONTROL },
});
} catch {
return jsonResponse(
{ error: "Registry item not found." },
{
status: 404,
headers: { "Cache-Control": STATIC_ASSET_CACHE_CONTROL },
},
);
}
}
44 changes: 44 additions & 0 deletions apps/web/src/pages/r/[style]/styles/index.json.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import fs from "node:fs/promises";
import path from "node:path";
import { STYLES } from "@bejamas/create-config/server";
import { jsonResponse } from "@/utils/create-registry";
import { STATIC_ASSET_CACHE_CONTROL } from "@/utils/http-cache";

export const prerender = true;

const staticStylesRoot = path.resolve(process.cwd(), "public/r/styles");

export function getStaticPaths() {
return STYLES.map((style) => ({
params: { style: style.id },
}));
}

export async function GET({ params }: { params: { style: string } }) {
const style = STYLES.find((entry) => entry.id === params.style);
if (!style) {
return jsonResponse(
{ error: "Style not found." },
{
status: 404,
headers: { "Cache-Control": STATIC_ASSET_CACHE_CONTROL },
},
);
}

try {
const filepath = path.resolve(staticStylesRoot, "index.json");
const payload = JSON.parse(await fs.readFile(filepath, "utf8"));
return jsonResponse(payload, {
headers: { "Cache-Control": STATIC_ASSET_CACHE_CONTROL },
});
} catch {
return jsonResponse(
{ error: "Registry styles not found." },
{
status: 404,
headers: { "Cache-Control": STATIC_ASSET_CACHE_CONTROL },
},
);
}
}
22 changes: 16 additions & 6 deletions apps/web/src/pages/r/styles/[style]/[name].json.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,23 +6,32 @@ import {
readStaticStyleRegistryItem,
} from "@/utils/create-registry";
import { STATIC_ASSET_CACHE_CONTROL } from "@/utils/http-cache";
import {
resolveRegistryStyleId,
SUPPORTED_REGISTRY_STYLE_IDS,
} from "@/utils/registry-style-compat";

export const prerender = true;

const staticStylesRoot = path.resolve(process.cwd(), "public/r/styles");
function getStaticStylesRoot() {
return path.resolve(process.cwd(), "public/r/styles");
}

export async function getStaticPaths() {
const paths = await Promise.all(
STYLES.map(async (style) => {
const filenames = await readdir(path.join(staticStylesRoot, style.id));
SUPPORTED_REGISTRY_STYLE_IDS.map(async (styleId) => {
const resolvedStyleId = resolveRegistryStyleId(styleId);
const filenames = await readdir(
path.join(getStaticStylesRoot(), resolvedStyleId),
);

return filenames
.filter(
(filename) => filename.endsWith(".json") && filename !== "index.json",
)
.map((filename) => ({
params: {
style: style.id,
style: styleId,
name: filename.slice(0, -".json".length),
},
}));
Expand All @@ -37,7 +46,8 @@ export async function GET({
}: {
params: { style: string; name: string };
}) {
const style = STYLES.find((entry) => entry.id === params.style);
const styleId = resolveRegistryStyleId(params.style);
const style = STYLES.find((entry) => entry.id === styleId);
if (!style) {
return jsonResponse(
{ error: "Style not found." },
Expand All @@ -59,7 +69,7 @@ export async function GET({
}

try {
const item = await readStaticStyleRegistryItem(style.id, params.name);
const item = await readStaticStyleRegistryItem(styleId, params.name);
return jsonResponse(item, {
headers: { "Cache-Control": STATIC_ASSET_CACHE_CONTROL },
});
Expand Down
13 changes: 9 additions & 4 deletions apps/web/src/pages/r/styles/[style]/index.json.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,22 @@ import {
readStaticStyleRegistryIndex,
} from "@/utils/create-registry";
import { STATIC_ASSET_CACHE_CONTROL } from "@/utils/http-cache";
import {
resolveRegistryStyleId,
SUPPORTED_REGISTRY_STYLE_IDS,
} from "@/utils/registry-style-compat";

export const prerender = true;

export function getStaticPaths() {
return STYLES.map((style) => ({
params: { style: style.id },
return SUPPORTED_REGISTRY_STYLE_IDS.map((style) => ({
params: { style },
}));
}

export async function GET({ params }: { params: { style: string } }) {
const style = STYLES.find((entry) => entry.id === params.style);
const styleId = resolveRegistryStyleId(params.style);
const style = STYLES.find((entry) => entry.id === styleId);

if (!style) {
return jsonResponse(
Expand All @@ -27,7 +32,7 @@ export async function GET({ params }: { params: { style: string } }) {
}

try {
return jsonResponse(await readStaticStyleRegistryIndex(style.id), {
return jsonResponse(await readStaticStyleRegistryIndex(styleId), {
headers: { "Cache-Control": STATIC_ASSET_CACHE_CONTROL },
});
} catch {
Expand Down
76 changes: 76 additions & 0 deletions apps/web/src/tests/pages/registry-legacy-style.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { describe, expect, test } from "bun:test";
import path from "node:path";
import {
GET as getStyleIndex,
getStaticPaths as getStyleIndexPaths,
} from "../../pages/r/styles/[style]/index.json";
import {
GET as getStyleItem,
getStaticPaths as getStyleItemPaths,
} from "../../pages/r/styles/[style]/[name].json";
import { STATIC_ASSET_CACHE_CONTROL } from "../../utils/http-cache";

const appRoot = path.resolve(import.meta.dir, "../../..");

describe("legacy registry style compatibility", () => {
test("prerenders new-york-v4 style item paths", async () => {
const previousCwd = process.cwd();
process.chdir(appRoot);

try {
const paths = await getStyleItemPaths();

expect(paths).toContainEqual({
params: { style: "new-york-v4", name: "button" },
});
} finally {
process.chdir(previousCwd);
}
});

test("serves Juno registry items for new-york-v4 requests", async () => {
const aliasResponse = await getStyleItem({
params: { style: "new-york-v4", name: "button" },
});
const junoResponse = await getStyleItem({
params: { style: "bejamas-juno", name: "button" },
});

expect(aliasResponse.status).toBe(200);
expect(aliasResponse.headers.get("Cache-Control")).toBe(
STATIC_ASSET_CACHE_CONTROL,
);

const aliasItem = (await aliasResponse.json()) as {
name: string;
};
const junoItem = (await junoResponse.json()) as {
name: string;
};

expect(aliasItem.name).toBe("button");
expect(aliasItem).toEqual(junoItem);
});

test("prerenders new-york-v4 style index paths", () => {
expect(getStyleIndexPaths()).toContainEqual({
params: { style: "new-york-v4" },
});
});

test("serves the Juno style index for new-york-v4 requests", async () => {
const response = await getStyleIndex({
params: { style: "new-york-v4" },
});

expect(response.status).toBe(200);

const item = (await response.json()) as {
type: string;
meta?: { styleId?: string };
};

expect(item.type).toBe("registry:style");
expect(item.meta?.styleId).toBe("bejamas-juno");
});
});
Loading