From fa7e6c4a986884d44bc5d434387d74ca01c13665 Mon Sep 17 00:00:00 2001 From: thom krupa Date: Mon, 13 Apr 2026 17:02:11 +0530 Subject: [PATCH 1/2] fix(cli): resolve add registry paths against active Bejamas style --- .changeset/warm-points-visit.md | 5 + .../src/pages/r/[style]/colors/[name].json.ts | 54 +++++++++++ .../styles/[requestedStyle]/[name].json.ts | 95 +++++++++++++++++++ .../src/pages/r/[style]/styles/index.json.ts | 41 ++++++++ .../src/tests/pages/static-rendering.test.ts | 49 ++++++++++ packages/bejamas/src/commands/add.ts | 40 +++++++- packages/bejamas/src/utils/ui-base-url.ts | 15 +++ packages/bejamas/test/add-output.test.ts | 8 ++ packages/bejamas/test/ui-base-url.test.ts | 10 ++ 9 files changed, 314 insertions(+), 3 deletions(-) create mode 100644 .changeset/warm-points-visit.md create mode 100644 apps/web/src/pages/r/[style]/colors/[name].json.ts create mode 100644 apps/web/src/pages/r/[style]/styles/[requestedStyle]/[name].json.ts create mode 100644 apps/web/src/pages/r/[style]/styles/index.json.ts diff --git a/.changeset/warm-points-visit.md b/.changeset/warm-points-visit.md new file mode 100644 index 00000000..373f6181 --- /dev/null +++ b/.changeset/warm-points-visit.md @@ -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. diff --git a/apps/web/src/pages/r/[style]/colors/[name].json.ts b/apps/web/src/pages/r/[style]/colors/[name].json.ts new file mode 100644 index 00000000..f87d589a --- /dev/null +++ b/apps/web/src/pages/r/[style]/colors/[name].json.ts @@ -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 colorsDir = 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(colorsDir, `${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 }, + }, + ); + } +} diff --git a/apps/web/src/pages/r/[style]/styles/[requestedStyle]/[name].json.ts b/apps/web/src/pages/r/[style]/styles/[requestedStyle]/[name].json.ts new file mode 100644 index 00000000..dc32086a --- /dev/null +++ b/apps/web/src/pages/r/[style]/styles/[requestedStyle]/[name].json.ts @@ -0,0 +1,95 @@ +import { readdir } from "node:fs/promises"; +import path from "node:path"; +import { + buildFontRegistryItem, + buildUtilsRegistryItem, + STYLES, +} from "@bejamas/create-config/server"; +import { + jsonResponse, + readStaticRegistryItem, + readStaticStyleRegistryItem, +} 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"); +const requestedStyleIds = Array.from( + new Set(["new-york", "new-york-v4", ...STYLES.map((style) => style.id)]), +); + +export async function getStaticPaths() { + const paths = await Promise.all( + STYLES.map(async (style) => { + const filenames = await 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 item = await readStaticStyleRegistryItem(style.id, params.name); + 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 item = await readStaticRegistryItem(params.name); + 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 }, + }, + ); + } +} diff --git a/apps/web/src/pages/r/[style]/styles/index.json.ts b/apps/web/src/pages/r/[style]/styles/index.json.ts new file mode 100644 index 00000000..a7beeb77 --- /dev/null +++ b/apps/web/src/pages/r/[style]/styles/index.json.ts @@ -0,0 +1,41 @@ +import { STYLES } from "@bejamas/create-config/server"; +import { + jsonResponse, + readStaticStyleRegistryStyles, +} from "@/utils/create-registry"; +import { STATIC_ASSET_CACHE_CONTROL } from "@/utils/http-cache"; + +export const prerender = true; + +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 { + return jsonResponse(await readStaticStyleRegistryStyles(), { + headers: { "Cache-Control": STATIC_ASSET_CACHE_CONTROL }, + }); + } catch { + return jsonResponse( + { error: "Registry styles not found." }, + { + status: 404, + headers: { "Cache-Control": STATIC_ASSET_CACHE_CONTROL }, + }, + ); + } +} diff --git a/apps/web/src/tests/pages/static-rendering.test.ts b/apps/web/src/tests/pages/static-rendering.test.ts index 45a3a1a7..33ecdc41 100644 --- a/apps/web/src/tests/pages/static-rendering.test.ts +++ b/apps/web/src/tests/pages/static-rendering.test.ts @@ -38,6 +38,18 @@ const styleRegistryItemRouteFile = path.resolve( import.meta.dir, "../../pages/r/styles/[style]/[name].json.ts", ); +const scopedColorRegistryRouteFile = path.resolve( + import.meta.dir, + "../../pages/r/[style]/colors/[name].json.ts", +); +const scopedStyleRegistryIndexRouteFile = path.resolve( + import.meta.dir, + "../../pages/r/[style]/styles/index.json.ts", +); +const scopedStyleRegistryItemRouteFile = path.resolve( + import.meta.dir, + "../../pages/r/[style]/styles/[requestedStyle]/[name].json.ts", +); const themeStylesheetRouteFile = path.resolve( import.meta.dir, "../../pages/r/themes/[slug].css.ts", @@ -110,6 +122,18 @@ describe("static rendering boundary", () => { styleRegistryItemRouteFile, "utf8", ); + const scopedColorRegistrySource = fs.readFileSync( + scopedColorRegistryRouteFile, + "utf8", + ); + const scopedStyleRegistryIndexSource = fs.readFileSync( + scopedStyleRegistryIndexRouteFile, + "utf8", + ); + const scopedStyleRegistrySource = fs.readFileSync( + scopedStyleRegistryItemRouteFile, + "utf8", + ); const themeStylesheetSource = fs.readFileSync( themeStylesheetRouteFile, "utf8", @@ -132,6 +156,31 @@ describe("static rendering boundary", () => { expect(styleRegistrySource).toContain("export const prerender = true;"); expect(styleRegistrySource).toContain("STATIC_ASSET_CACHE_CONTROL"); expect(styleRegistrySource).not.toContain("fileURLToPath"); + expect(scopedColorRegistrySource).toContain( + 'path.resolve(process.cwd(), "public/r/colors")', + ); + expect(scopedColorRegistrySource).toContain("export const prerender = true;"); + expect(scopedColorRegistrySource).toContain("STATIC_ASSET_CACHE_CONTROL"); + expect(scopedStyleRegistryIndexSource).toContain( + "export const prerender = true;", + ); + expect(scopedStyleRegistryIndexSource).toContain( + "readStaticStyleRegistryStyles", + ); + expect(scopedStyleRegistryIndexSource).toContain( + "STATIC_ASSET_CACHE_CONTROL", + ); + expect(scopedStyleRegistrySource).toContain( + 'path.resolve(process.cwd(), "public/r/styles")', + ); + expect(scopedStyleRegistrySource).toContain( + "export const prerender = true;", + ); + expect(scopedStyleRegistrySource).toContain( + "STATIC_ASSET_CACHE_CONTROL", + ); + expect(scopedStyleRegistrySource).toContain("requestedStyleIds"); + expect(scopedStyleRegistrySource).not.toContain("fileURLToPath"); expect(themeStylesheetSource).toContain("export const prerender = true;"); expect(themeStylesheetSource).toContain("STATIC_ASSET_CACHE_CONTROL"); expect(blockRouteSource).toContain("export const prerender = true;"); diff --git a/packages/bejamas/src/commands/add.ts b/packages/bejamas/src/commands/add.ts index da4ba985..11454ba3 100644 --- a/packages/bejamas/src/commands/add.ts +++ b/packages/bejamas/src/commands/add.ts @@ -2,6 +2,7 @@ import path from "node:path"; import { Command } from "commander"; import { execa } from "execa"; import prompts from "prompts"; +import { STYLES } from "@bejamas/create-config/server"; import { syncAstroManagedFontCss, @@ -29,7 +30,10 @@ import { ensurePinnedShadcnExecPrefix, } from "@/src/utils/shadcn-cli"; import { spinner } from "@/src/utils/spinner"; -import { resolveRegistryUrl } from "@/src/utils/ui-base-url"; +import { + buildStyleScopedRegistryUrl, + resolveRegistryUrl, +} from "@/src/utils/ui-base-url"; interface ParsedOutput { created: string[]; @@ -37,6 +41,29 @@ interface ParsedOutput { skipped: string[]; } +const DEFAULT_REGISTRY_STYLE = "bejamas-juno"; +const styleNameToId = new Map(STYLES.map((style) => [style.name, style.id])); +const knownStyleIds = new Set(STYLES.map((style) => style.id)); + +export function resolveRegistryStyle(style?: string | null) { + const normalizedStyle = style?.trim(); + + if (!normalizedStyle) { + return DEFAULT_REGISTRY_STYLE; + } + + if (knownStyleIds.has(normalizedStyle)) { + return normalizedStyle; + } + + const mappedStyleId = styleNameToId.get(normalizedStyle); + if (mappedStyleId) { + return mappedStyleId; + } + + return DEFAULT_REGISTRY_STYLE; +} + // Derive only the user-provided flags for shadcn to avoid losing options. export function extractOptionsForShadcn( rawArgv: string[], @@ -386,10 +413,15 @@ async function addComponents( isVerbose: boolean, isSilent: boolean, inspectionMode: boolean, + style: string, ): Promise { + const shadcnRegistryUrl = buildStyleScopedRegistryUrl( + resolveRegistryUrl(), + style, + ); const env = { ...process.env, - REGISTRY_URL: resolveRegistryUrl(), + REGISTRY_URL: shadcnRegistryUrl, }; await ensurePinnedShadcnExecPrefix(); const shadcnArgs = buildShadcnAddArgs(packages, forwardedOptions); @@ -397,6 +429,7 @@ async function addComponents( if (isVerbose) { logger.info(`[bejamas-ui] ${invocation.cmd} ${invocation.args.join(" ")}`); + logger.info(`[bejamas-ui] shadcn registry: ${shadcnRegistryUrl}`); } const registrySpinner = spinner("Checking registry.", { silent: isSilent }); @@ -554,7 +587,7 @@ export const add = new Command() ); } - const activeStyle = uiConfig?.style || config?.style || "bejamas-juno"; + const activeStyle = resolveRegistryStyle(uiConfig?.style || config?.style); const totalComponents = componentsToAdd.length; for (let index = 0; index < componentsToAdd.length; index += 1) { @@ -583,6 +616,7 @@ export const add = new Command() verbose, isSilent, inspectionMode, + activeStyle, ); if (!inspectionMode) { diff --git a/packages/bejamas/src/utils/ui-base-url.ts b/packages/bejamas/src/utils/ui-base-url.ts index 335d0d23..11d26235 100644 --- a/packages/bejamas/src/utils/ui-base-url.ts +++ b/packages/bejamas/src/utils/ui-base-url.ts @@ -5,6 +5,10 @@ function normalizeUrl(url: string) { return url.trim().replace(/\/+$/, ""); } +function normalizePathSegment(segment: string) { + return segment.trim().replace(/^\/+|\/+$/g, ""); +} + function deriveUiBaseUrlFromRegistryUrl(registryUrl: string) { const normalizedRegistryUrl = normalizeUrl(registryUrl); return normalizedRegistryUrl.endsWith("/r") @@ -36,6 +40,17 @@ export function resolveRegistryUrl(env: NodeJS.ProcessEnv = process.env) { return DEFAULT_REGISTRY_URL; } +export function buildStyleScopedRegistryUrl( + registryUrl: string, + style: string, +) { + const normalizedStyle = normalizePathSegment(style); + + return normalizedStyle + ? `${normalizeUrl(registryUrl)}/${normalizedStyle}` + : normalizeUrl(registryUrl); +} + export function buildUiUrl( pathname: string, env: NodeJS.ProcessEnv = process.env, diff --git a/packages/bejamas/test/add-output.test.ts b/packages/bejamas/test/add-output.test.ts index dcd4b60e..f92349b7 100644 --- a/packages/bejamas/test/add-output.test.ts +++ b/packages/bejamas/test/add-output.test.ts @@ -5,6 +5,7 @@ import { extractOptionsForShadcn, formatSkippedFilesHeading, hasInspectionFlags, + resolveRegistryStyle, } from "../src/commands/add"; function createAddLikeCommand() { @@ -134,4 +135,11 @@ describe("add output helpers", () => { "Already has newline\n", ); }); + + test("normalizes supported Bejamas styles and falls back for upstream styles", () => { + expect(resolveRegistryStyle("bejamas-juno")).toBe("bejamas-juno"); + expect(resolveRegistryStyle("vega")).toBe("bejamas-vega"); + expect(resolveRegistryStyle("new-york-v4")).toBe("bejamas-juno"); + expect(resolveRegistryStyle(undefined)).toBe("bejamas-juno"); + }); }); diff --git a/packages/bejamas/test/ui-base-url.test.ts b/packages/bejamas/test/ui-base-url.test.ts index bf78c69d..8fc478ba 100644 --- a/packages/bejamas/test/ui-base-url.test.ts +++ b/packages/bejamas/test/ui-base-url.test.ts @@ -1,5 +1,6 @@ import { describe, expect, test } from "bun:test"; import { + buildStyleScopedRegistryUrl, DEFAULT_REGISTRY_URL, DEFAULT_UI_BASE_URL, resolveRegistryUrl, @@ -31,4 +32,13 @@ describe("CLI UI URL resolution", () => { expect(resolveRegistryUrl(env)).toBe("http://localhost:4322/r"); expect(resolveUiBaseUrl(env)).toBe("http://localhost:4322"); }); + + test("builds a style-scoped registry URL for shadcn installs", () => { + expect( + buildStyleScopedRegistryUrl("https://ui.bejamas.com/r/", "bejamas-juno"), + ).toBe("https://ui.bejamas.com/r/bejamas-juno"); + expect( + buildStyleScopedRegistryUrl("https://ui.bejamas.com/r", "/bejamas-vega/"), + ).toBe("https://ui.bejamas.com/r/bejamas-vega"); + }); }); From e0463918e3ab681b6c865294acc63573ee3deb08 Mon Sep 17 00:00:00 2001 From: thom krupa Date: Mon, 13 Apr 2026 17:29:07 +0530 Subject: [PATCH 2/2] fix(web): alias legacy new-york-v4 registry paths to juno --- .../src/pages/r/[style]/colors/[name].json.ts | 4 +- .../styles/[requestedStyle]/[name].json.ts | 22 +++--- .../src/pages/r/[style]/styles/index.json.ts | 13 ++-- .../src/pages/r/styles/[style]/[name].json.ts | 22 ++++-- .../src/pages/r/styles/[style]/index.json.ts | 13 +++- .../tests/pages/registry-legacy-style.test.ts | 76 +++++++++++++++++++ .../src/tests/pages/static-rendering.test.ts | 2 +- apps/web/src/utils/registry-style-compat.ts | 33 ++++++++ 8 files changed, 155 insertions(+), 30 deletions(-) create mode 100644 apps/web/src/tests/pages/registry-legacy-style.test.ts create mode 100644 apps/web/src/utils/registry-style-compat.ts diff --git a/apps/web/src/pages/r/[style]/colors/[name].json.ts b/apps/web/src/pages/r/[style]/colors/[name].json.ts index f87d589a..8252bf38 100644 --- a/apps/web/src/pages/r/[style]/colors/[name].json.ts +++ b/apps/web/src/pages/r/[style]/colors/[name].json.ts @@ -6,7 +6,7 @@ import { STATIC_ASSET_CACHE_CONTROL } from "@/utils/http-cache"; export const prerender = true; -const colorsDir = path.resolve(process.cwd(), "public/r/colors"); +const staticColorsRoot = path.resolve(process.cwd(), "public/r/colors"); export function getStaticPaths() { return STYLES.flatMap((style) => @@ -35,7 +35,7 @@ export async function GET({ ); } - const filepath = path.resolve(colorsDir, `${params.name}.json`); + const filepath = path.resolve(staticColorsRoot, `${params.name}.json`); try { const payload = JSON.parse(await fs.readFile(filepath, "utf8")); diff --git a/apps/web/src/pages/r/[style]/styles/[requestedStyle]/[name].json.ts b/apps/web/src/pages/r/[style]/styles/[requestedStyle]/[name].json.ts index dc32086a..f3b02576 100644 --- a/apps/web/src/pages/r/[style]/styles/[requestedStyle]/[name].json.ts +++ b/apps/web/src/pages/r/[style]/styles/[requestedStyle]/[name].json.ts @@ -1,28 +1,24 @@ -import { readdir } from "node:fs/promises"; +import fs from "node:fs/promises"; import path from "node:path"; import { buildFontRegistryItem, buildUtilsRegistryItem, STYLES, } from "@bejamas/create-config/server"; -import { - jsonResponse, - readStaticRegistryItem, - readStaticStyleRegistryItem, -} from "@/utils/create-registry"; +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 requestedStyleIds = Array.from( - new Set(["new-york", "new-york-v4", ...STYLES.map((style) => style.id)]), -); +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 readdir(path.join(staticStylesRoot, style.id)); + 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)); @@ -59,7 +55,8 @@ export async function GET({ } try { - const item = await readStaticStyleRegistryItem(style.id, params.name); + 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 }, }); @@ -79,7 +76,8 @@ export async function GET({ } try { - const item = await readStaticRegistryItem(params.name); + 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 }, }); diff --git a/apps/web/src/pages/r/[style]/styles/index.json.ts b/apps/web/src/pages/r/[style]/styles/index.json.ts index a7beeb77..86ed44c4 100644 --- a/apps/web/src/pages/r/[style]/styles/index.json.ts +++ b/apps/web/src/pages/r/[style]/styles/index.json.ts @@ -1,12 +1,13 @@ +import fs from "node:fs/promises"; +import path from "node:path"; import { STYLES } from "@bejamas/create-config/server"; -import { - jsonResponse, - readStaticStyleRegistryStyles, -} from "@/utils/create-registry"; +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 }, @@ -26,7 +27,9 @@ export async function GET({ params }: { params: { style: string } }) { } try { - return jsonResponse(await readStaticStyleRegistryStyles(), { + 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 { diff --git a/apps/web/src/pages/r/styles/[style]/[name].json.ts b/apps/web/src/pages/r/styles/[style]/[name].json.ts index be14e3fe..27d0ff03 100644 --- a/apps/web/src/pages/r/styles/[style]/[name].json.ts +++ b/apps/web/src/pages/r/styles/[style]/[name].json.ts @@ -6,15 +6,24 @@ 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( @@ -22,7 +31,7 @@ export async function getStaticPaths() { ) .map((filename) => ({ params: { - style: style.id, + style: styleId, name: filename.slice(0, -".json".length), }, })); @@ -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." }, @@ -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 }, }); diff --git a/apps/web/src/pages/r/styles/[style]/index.json.ts b/apps/web/src/pages/r/styles/[style]/index.json.ts index f069e6d8..1d965be6 100644 --- a/apps/web/src/pages/r/styles/[style]/index.json.ts +++ b/apps/web/src/pages/r/styles/[style]/index.json.ts @@ -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( @@ -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 { diff --git a/apps/web/src/tests/pages/registry-legacy-style.test.ts b/apps/web/src/tests/pages/registry-legacy-style.test.ts new file mode 100644 index 00000000..6735afbf --- /dev/null +++ b/apps/web/src/tests/pages/registry-legacy-style.test.ts @@ -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"); + }); +}); diff --git a/apps/web/src/tests/pages/static-rendering.test.ts b/apps/web/src/tests/pages/static-rendering.test.ts index 33ecdc41..0823af3a 100644 --- a/apps/web/src/tests/pages/static-rendering.test.ts +++ b/apps/web/src/tests/pages/static-rendering.test.ts @@ -165,7 +165,7 @@ describe("static rendering boundary", () => { "export const prerender = true;", ); expect(scopedStyleRegistryIndexSource).toContain( - "readStaticStyleRegistryStyles", + 'path.resolve(process.cwd(), "public/r/styles")', ); expect(scopedStyleRegistryIndexSource).toContain( "STATIC_ASSET_CACHE_CONTROL", diff --git a/apps/web/src/utils/registry-style-compat.ts b/apps/web/src/utils/registry-style-compat.ts new file mode 100644 index 00000000..764058ed --- /dev/null +++ b/apps/web/src/utils/registry-style-compat.ts @@ -0,0 +1,33 @@ +import { STYLES } from "@bejamas/create-config/browser"; + +export const LEGACY_REGISTRY_STYLE_ALIASES = { + "new-york-v4": "bejamas-juno", +} as const; + +export const LEGACY_REQUESTED_STYLE_IDS = [ + "new-york", + ...Object.keys(LEGACY_REGISTRY_STYLE_ALIASES), +]; + +export const SUPPORTED_REGISTRY_STYLE_IDS = Array.from( + new Set([ + ...STYLES.map((style) => style.id), + ...Object.keys(LEGACY_REGISTRY_STYLE_ALIASES), + ]), +); + +export const REQUESTED_STYLE_IDS = Array.from( + new Set([...LEGACY_REQUESTED_STYLE_IDS, ...STYLES.map((style) => style.id)]), +); + +const resolvedRegistryStyleIds = new Set(STYLES.map((style) => style.id)); + +export function resolveRegistryStyleId(style: string) { + return LEGACY_REGISTRY_STYLE_ALIASES[ + style as keyof typeof LEGACY_REGISTRY_STYLE_ALIASES + ] ?? style; +} + +export function isSupportedRegistryStyleId(style: string) { + return resolvedRegistryStyleIds.has(resolveRegistryStyleId(style)); +}