Files
huntabyte__shadcn-svelte/docs/scripts/build-registry.ts
2025-06-20 10:24:52 -04:00

235 lines
7.6 KiB
TypeScript

import template from "lodash.template";
import fs from "node:fs";
import path from "node:path";
import prettier from "prettier";
import { rimraf } from "rimraf";
import {
componentsJsonSchema,
registryItemSchema,
registrySchema,
type Registry,
type RegistryItem,
type RegistryItemType,
} from "@shadcn-svelte/registry";
import { generateBaseColorTemplate, getColorsData } from "../src/lib/components/colors/colors.js";
import { buildRegistry } from "./registry.js";
import { baseColors } from "../src/lib/registry/registry-colors.js";
import { THEME_STYLES_WITH_VARIABLES } from "../src/lib/registry/templates.js";
import { baseColorsOKLCH } from "../src/lib/registry/registry-base-colors.js";
import { toJSONSchema } from "zod/v4";
const prettierConfig = await prettier.resolveConfig(import.meta.url);
if (!prettierConfig) throw new Error("Failed to resolve prettier config.");
const REGISTRY_PATH = path.resolve("static", "registry");
const THEMES_CSS_PATH = path.resolve("static");
function writeFileWithDirs(
filePath: string,
data: string,
options: Parameters<typeof fs.writeFileSync>[2] = {}
): void {
// Create directory path if it doesn't exist
const dirname = path.dirname(filePath);
fs.mkdirSync(dirname, { recursive: true });
// Write the file
fs.writeFileSync(filePath, data, options);
}
export async function build(): Promise<void> {
const registry = await buildRegistry();
const selfReferenced = registry.filter((item) => item.registryDependencies.includes(item.name));
const selfReferenceError = selfReferenced
.map((item) => `Registry item '${item.name}' depends on itself`)
.join("\n");
if (selfReferenceError) {
throw new Error(selfReferenceError);
}
const initItem: RegistryItem = {
name: "init",
type: "registry:style",
devDependencies: ["tailwind-variants", "@lucide/svelte", "tw-animate-css"],
registryDependencies: ["utils"],
files: [],
};
// ----------------------------------------------------------------------------
// Build `registry.json` file.
// ----------------------------------------------------------------------------
const result = registrySchema.parse(
{
$schema: "./static/schema/registry.json",
name: "shadcn-svelte",
homepage: "https://shadcn-svelte.com",
aliases: {
lib: "$lib/registry/lib",
ui: "$lib/registry/ui",
components: "./components",
hooks: "$lib/registry/hooks",
utils: "$lib/utils",
},
// TODO: remove when moving from `next` to `latest`
overrideDependencies: ["vaul-svelte@next"],
items: [initItem, ...registry],
} as Registry,
// maintains the schema defined property order
{ jitless: true }
);
const ITEM_TYPES: RegistryItemType[] = [
"registry:ui",
"registry:hook",
"registry:style",
"registry:lib",
"registry:block",
];
const filteredItems = result.items.filter((item) => ITEM_TYPES.includes(item.type));
const registryJsonPath = path.resolve("registry.json");
const registryJson = JSON.stringify({ ...result, items: filteredItems }, null, "\t");
const formatted = await prettier.format(registryJson, {
...prettierConfig,
filepath: registryJsonPath,
});
fs.writeFileSync(registryJsonPath, formatted, "utf8");
// ----------------------------------------------------------------------------
// Build __registry__/blocks.ts
// ----------------------------------------------------------------------------
rimraf.sync(path.resolve("src", "__registry__"));
let blocksIndex = `
// This file is autogenerated by scripts/build-registry.ts
// Do not edit this file directly.
export const blocks = [
`; // Creates block index files
for (const block of result.items) {
if (block.type !== "registry:block" || block.name.startsWith("chart-")) continue;
blocksIndex += `"${block.name}",`;
}
blocksIndex += "\n] as const;\n";
const blocksPath = path.resolve("src", "__registry__", "blocks.ts");
writeFileWithDirs(blocksPath, blocksIndex);
// ----------------------------------------------------------------------------
// Build __registry__/index.js.
// ----------------------------------------------------------------------------
let index = `
// This file is autogenerated by scripts/build-registry.ts
// Do not edit this file directly.
export const Index = {`;
// TODO: fix later - we should either add these calendar examples to
// the `lib/registry/examples` dir, or... do something else?
const CALENDAR_EXAMPLES = ["02", "13", "22", "24", "29"].map((n) => `calendar-${n}`);
// Build style index.
for (const item of result.items) {
if (item.type !== "registry:example" && !CALENDAR_EXAMPLES.includes(item.name)) {
continue;
}
const resolveFiles = item.files.map((file) => file.path.replace("src/", "../"));
index += `
"${item.name}": {
files: [${resolveFiles.map((file) => `"${file.replaceAll(path.sep, "/")}"`)}],
},`;
}
index += `
}
`;
// Write style index.
const registryPath = path.resolve("src", "__registry__", "index.js");
rimraf.sync(registryPath);
writeFileWithDirs(registryPath, index);
// ----------------------------------------------------------------------------
// Build registry/colors/index.json.
// ----------------------------------------------------------------------------
const colorsTargetPath = path.join(REGISTRY_PATH, "colors");
rimraf.sync(colorsTargetPath);
if (!fs.existsSync(colorsTargetPath)) {
fs.mkdirSync(colorsTargetPath, { recursive: true });
}
const colorsData = getColorsData();
writeFileWithDirs(
path.join(colorsTargetPath, "index.json"),
JSON.stringify(colorsData, null, "\t"),
"utf-8"
);
// ----------------------------------------------------------------------------
// Build registry/colors/[base].json.
// ----------------------------------------------------------------------------
const themeCSS = [];
for (const baseColor of baseColors) {
const base = generateBaseColorTemplate(baseColor);
const zincCssVars = generateBaseColorTemplate("zinc");
themeCSS.push(
template(THEME_STYLES_WITH_VARIABLES)({
colors: {
...zincCssVars.cssVars,
...baseColorsOKLCH[baseColor as keyof typeof baseColorsOKLCH],
},
theme: baseColor,
})
);
if (["zinc", "stone", "slate", "gray", "neutral"].includes(baseColor)) {
writeFileWithDirs(
path.join(REGISTRY_PATH, "colors", `${baseColor}.json`),
JSON.stringify(base, null, "\t"),
"utf-8"
);
}
}
// ----------------------------------------------------------------------------
// Build registry/themes.css
// ----------------------------------------------------------------------------
writeFileWithDirs(path.join(THEMES_CSS_PATH, `themes.css`), themeCSS.join("\n\n"), "utf-8");
// ----------------------------------------------------------------------------
// Build static/schema.json
// ----------------------------------------------------------------------------
const componentsJSON = toJSONSchema(componentsJsonSchema);
writeFileWithDirs(
path.resolve("static", "schema.json"),
JSON.stringify(componentsJSON, null, "\t")
);
// ----------------------------------------------------------------------------
// Build static/schema/registry.json
// ----------------------------------------------------------------------------
const SCHEMA_DIR = path.resolve("static", "schema");
writeFileWithDirs(
path.resolve(SCHEMA_DIR, "registry.json"),
JSON.stringify(toJSONSchema(registrySchema), null, "\t")
);
// ----------------------------------------------------------------------------
// Build static/schema/registry-item.json
// ----------------------------------------------------------------------------
writeFileWithDirs(
path.resolve(SCHEMA_DIR, "registry-item.json"),
JSON.stringify(toJSONSchema(registryItemSchema), null, "\t")
);
}
if (process.argv.includes("build-registry")) {
build();
}