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
9 changes: 9 additions & 0 deletions config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
buildConfiguration:
buildCommand: cd src/components/puck/registry && npm run generate
benlife5 marked this conversation as resolved.
Show resolved Hide resolved
installDependenciesStep:
command: cd src/components/puck/registry && npm install
requiredFiles:
- src/components/puck/registry/package.json
- src/components/puck/registry/package-lock.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.

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/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
32 changes: 32 additions & 0 deletions src/components/puck/registry/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Component Shadcn Registry

## Generate registry locally

`cd src/components/puck/registry && npm i && npm run generate`

## 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
- 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 the our registry with the shadcn CLI.
benlife5 marked this conversation as resolved.
Show resolved Hide resolved

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/**/*"
}
]
}
}
141 changes: 141 additions & 0 deletions src/components/puck/registry/build-registry.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
// 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, mkdirSync } 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 COLOR_PATH = `${DIST_DIR}/${SLUG}/colors`;
const ICONS_PATH = `${DIST_DIR}/${SLUG}/icons`;
const THEMES_PATH = `${DIST_DIR}/${SLUG}/styles`;
const COMPONENT_PATH = `${THEMES_PATH}/default`;

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

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

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

const iconsIndex = {};

const themeIndex = [{ name: "default", label: "Default" }];

type File = z.infer<typeof registryItemFileSchema>;

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 });

// Write the file
await writeFile(filePath, data, "utf-8");
} catch (error) {
console.error("Error writing file: ", error);
}
}

const getComponentFiles = async (files: File[]) => {
const filesArrayPromises = (files ?? []).map(async (file) => {
if (typeof file === "string") {
const filePath = `../${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 });
}

// Create directories if they do not exist
for (const path of [COMPONENT_PATH, COLOR_PATH, ICONS_PATH]) {
if (!existsSync(path)) {
mkdirSync(path, { recursive: true });
}
}

// Write index files
await writeFile(`${COMPONENT_PATH}/index.json`, JSON.stringify(styleIndex));
await writeFile(`${COLOR_PATH}/neutral.json`, JSON.stringify(colorIndex));
await writeFile(`${ICONS_PATH}/index.json`, JSON.stringify(iconsIndex));
await writeFile(`${THEMES_PATH}/index.json`, JSON.stringify(themeIndex));
await copyFile(`../registry/artifacts.json`, `${DIST_DIR}/artifacts.json`);

const componentNames = ["index"];

// generate JSON files for each component
for (let i = 0; i < registryComponents.length; i++) {
const component = registryComponents[i];
componentNames.push(component.name);
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
);
const jsonPath = `${COMPONENT_PATH}/${component.name}.json`;
await writeFileRecursive(jsonPath, json);
}

// write and export the top level index file
await writeFileRecursive(
`${DIST_DIR}/${SLUG}/index.json`,
JSON.stringify(registryComponents)
);
await writeFileRecursive(
`${DIST_DIR}/${SLUG}/registry/index.json`,
JSON.stringify(registryComponents)
);
};

buildRegistry()
.then(() => {
console.log("Registry files created successfully");
})
.catch((err) => {
console.error("Error creating registry files: ", err);
});
18 changes: 18 additions & 0 deletions src/components/puck/registry/components.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
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"],
},
];
Loading