Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add shadcn cli registry #138

Merged
merged 15 commits into from
Nov 8, 2024
10 changes: 10 additions & 0 deletions config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
buildConfiguration:
buildCommand: pnpm run generate-registry
installDependenciesStep:
command: npm install -g pnpm && pnpm i --ignore-scripts
requiredFiles:
- package.json
- pnpm-lock.yaml
- tsconfig.json
livePreviewConfiguration:
setupCommand: ":"
6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@
"ci-publish": "tsx scripts/publishCI.ts",
"release": "tsx scripts/release.ts",
"prepare": "husky && pnpm build",
"generate-notices": "generate-license-file"
"generate-notices": "generate-license-file",
"generate-registry": "tsx src/components/puck/registry/build-registry.ts"
},
"dependencies": {
"@measured/puck": "0.16.2-canary.a1988b5",
Expand Down Expand Up @@ -109,7 +110,8 @@
"typescript": "^5.5.4",
"vite": "^5.3.5",
"vite-tsconfig-paths": "^4.3.2",
"vitest": "^2.0.5"
"vitest": "^2.0.5",
"zod": "^3.23.8"
},
"peerDependencies": {
"react": "^17.0.2 || ^18.2.0",
Expand Down
3 changes: 3 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 6 additions & 6 deletions src/components/puck/BodyText.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import * as React from "react";
import { ComponentConfig, Fields } from "@measured/puck";
import { Body, BodyProps, bodyVariants } from "./atoms/body.tsx";
import { useDocument } from "../../hooks/useDocument.tsx";
import { resolveYextEntityField } from "../../utils/resolveYextEntityField.ts";
import { EntityField } from "../editor/EntityField.tsx";
import { Body, BodyProps, bodyVariants } from "./atoms/body.js";
import {
useDocument,
resolveYextEntityField,
EntityField,
YextEntityField,
YextEntityFieldSelector,
} from "../editor/YextEntityFieldSelector.tsx";
import { NumberFieldWithDefaultOption } from "../editor/NumberOrDefaultField.tsx";
NumberFieldWithDefaultOption,
} from "../../index.ts";

export interface BodyTextProps extends BodyProps {
text: YextEntityField<string>;
Expand Down
14 changes: 7 additions & 7 deletions src/components/puck/HeadingText.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import React from "react";
import * as React from "react";
import { ComponentConfig, Fields } from "@measured/puck";
import { Heading, HeadingProps, headingVariants } from "./atoms/heading.tsx";
import { useDocument } from "../../hooks/useDocument.tsx";
import { resolveYextEntityField } from "../../utils/resolveYextEntityField.ts";
import { EntityField } from "../editor/EntityField.tsx";
import { Heading, HeadingProps, headingVariants } from "./atoms/heading.js";
import {
useDocument,
resolveYextEntityField,
EntityField,
YextEntityField,
YextEntityFieldSelector,
} from "../editor/YextEntityFieldSelector.tsx";
import { NumberFieldWithDefaultOption } from "../editor/NumberOrDefaultField.tsx";
NumberFieldWithDefaultOption,
} from "../../index.ts";

export interface HeadingTextProps extends HeadingProps {
text: YextEntityField<string>;
Expand Down
2 changes: 1 addition & 1 deletion src/components/puck/atoms/body.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as React from "react";
import { cva, type VariantProps } from "class-variance-authority";
import clsx from "clsx";
import { clsx } from "clsx";
import { NumberOrDefault } from "../../editor/NumberOrDefaultField.tsx";

// Define the variants for the body component
Expand Down
2 changes: 1 addition & 1 deletion src/components/puck/atoms/heading.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as React from "react";
import { cva, type VariantProps } from "class-variance-authority";
import { type NumberOrDefault } from "../../editor/NumberOrDefaultField.tsx";
import { type NumberOrDefault } from "../../editor/NumberOrDefaultField.js";
benlife5 marked this conversation as resolved.
Show resolved Hide resolved

// Define the variants for the heading component
const headingVariants = cva("components", {
Expand Down
33 changes: 33 additions & 0 deletions src/components/puck/registry/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Component Shadcn Registry

## Generate registry locally

`pnpm run generate-registry`

## Add a new component

Add a new object to `ui` in `components.ts`.

| Field | Description |
| -------------------- | -------------------------------------------------------------------------------------------------------------------------- |
| name | must be unique |
| type | "registry:ui" if it should be directly pulled by a user. "registry:component" if it's a sub-component of multiple ui files |
| files | array of files relative to `src/components/puck` |
| registryDependencies | array of component dependencies using the names defined in this file |

## Requirements for components

- Do not use .ts or .tsx imports because of the starter's tsconfig
- Default package imports should be avoided because of the starter's tsconfig
- Imports from the @yext/visual-editor package must have a local path that is captured by the
`IMPORT_PATTERN` in `build-registry.ts`

## Running shadcn locally

Useful for debugging our registry with the shadcn CLI.

1. `git clone https://github.com/shadcn-ui/ui.git`
2. `pnpm i`
3. `cd packages/shadcn` (`packages/cli` is the old cli)
4. `pnpm run dev`
5. `REGISTRY_URL=https://reliably-numerous-kit.pgsdemo.com/components node ui/packages/shadcn/dist/index.js add`
10 changes: 10 additions & 0 deletions src/components/puck/registry/artifacts.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"artifactStructure": {
"assets": [
{
"root": "dist",
"pattern": "components/**/*"
}
]
}
}
143 changes: 143 additions & 0 deletions src/components/puck/registry/build-registry.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
// Based on https://github.com/bwestwood11/ui-cart/blob/main/scripts/build-registry.ts
// which is a simplified version of https://github.com/shadcn-ui/ui/blob/main/apps/www/scripts/build-registry.mts
import { writeFile, copyFile, mkdir, readFile } from "node:fs/promises";
import { existsSync, rmSync } from "fs";
import z from "zod";
import path from "path";
import { registryComponents } from "./registry.ts";
import { registryItemFileSchema } from "./schema.ts";

const DIST_DIR = `./dist`;
const SLUG = `components`;
const COLORS_PATH = `${DIST_DIR}/${SLUG}/colors`;
const ICONS_PATH = `${DIST_DIR}/${SLUG}/icons`;
const STYLES_PATH = `${DIST_DIR}/${SLUG}/styles`;
const YEXT_STYLE_PATH = `${STYLES_PATH}/yext`;
const COMPONENTS_SRC_PATH = `./src/components/puck`;

// matches import { ... } from "..." where the import path starts with ../../
const IMPORT_PATTERN = /from "(\.\.\/\.\.\/[^"]+)"/g;

const styleIndex = {
name: "yext",
type: "registry:style",
cssVars: {},
files: [],
};

const colorsIndex = {
inlineColors: {
light: {},
dark: {},
},
cssVars: {
light: {},
dark: {},
},
inlineColorsTemplate: "",
cssVarsTemplate: "",
};

const iconsIndex = {};

const stylesIndex = [{ name: "yext", label: "Yext" }];

async function writeFileRecursive(filePath: string, data: string) {
const dir = path.dirname(filePath); // Extract the directory path

try {
// Ensure the directory exists, recursively creating directories as needed
await mkdir(dir, { recursive: true });
await writeFile(filePath, data, "utf-8");
} catch (error) {
console.error("Error writing file: ", error);
}
}

type File = z.infer<typeof registryItemFileSchema>;
// Read the files specified in registry.ts, insert the @yext/visual-editor import and output JSON
const getComponentFiles = async (files: File[]) => {
const filesArrayPromises = (files ?? []).map(async (file) => {
if (typeof file === "string") {
const filePath = `${COMPONENTS_SRC_PATH}/${file}`;
const fileContent = await readFile(filePath, "utf-8");
return {
type: "registry:component",
content: fileContent.replace(
IMPORT_PATTERN,
`from "@yext/visual-editor"`
),
path: file,
target: `${SLUG}/${file}`,
};
}
});
const filesArray = await Promise.all(filesArrayPromises);

return filesArray;
};

export const buildRegistry = async () => {
// Delete dist folder if it exists
if (existsSync(DIST_DIR)) {
rmSync(DIST_DIR, { recursive: true });
}

// Write all index files
await Promise.all([
writeFileRecursive(
`${YEXT_STYLE_PATH}/index.json`,
JSON.stringify(styleIndex)
),
writeFileRecursive(
`${COLORS_PATH}/neutral.json`,
JSON.stringify(colorsIndex)
),
writeFileRecursive(`${ICONS_PATH}/index.json`, JSON.stringify(iconsIndex)),
writeFileRecursive(
`${STYLES_PATH}/index.json`,
JSON.stringify(stylesIndex)
),
writeFileRecursive(
`${DIST_DIR}/${SLUG}/index.json`,
JSON.stringify(registryComponents)
),
writeFileRecursive(
`${DIST_DIR}/${SLUG}/registry/index.json`,
JSON.stringify(registryComponents)
),
]);

// copy artifacts.json after to ensure that the `dist` dir exists
await copyFile(
`${COMPONENTS_SRC_PATH}/registry/artifacts.json`,
`${DIST_DIR}/artifacts.json`
);

// generate JSON files for each component
for (let i = 0; i < registryComponents.length; i++) {
const component = registryComponents[i];
const files = component.files;
if (!files) throw new Error("No files found for component");

const filesArray = await getComponentFiles(files);

const json = JSON.stringify(
{
...component,
files: filesArray,
},
null,
2
);
await writeFileRecursive(`${YEXT_STYLE_PATH}/${component.name}.json`, json);
}
};

buildRegistry()
.then(() => {
console.log("Registry files created successfully");
})
.catch((err) => {
console.error("Error creating registry files: ", err);
});
29 changes: 29 additions & 0 deletions src/components/puck/registry/components.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { Registry } from "./schema.ts";

// type: registry:ui => appears in the `npx shadcn add` list
// type: registry:component => building blocks that shouldn't be used on their own

export const ui: Registry = [
{
name: "heading",
type: "registry:component",
files: ["atoms/heading.tsx"],
},
{
name: "HeadingText",
type: "registry:ui",
registryDependencies: ["heading"],
files: ["HeadingText.tsx"],
},
{
name: "body",
type: "registry:component",
files: ["atoms/body.tsx"],
},
{
name: "BodyText",
type: "registry:ui",
registryDependencies: ["body"],
files: ["BodyText.tsx"],
},
];
4 changes: 4 additions & 0 deletions src/components/puck/registry/registry.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { ui } from "./components.ts";

// Allows for later expansion into other shadcn registry types
export const registryComponents = [...ui];
Loading