@sudajs/cli 0.9.0 → 0.9.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sudajs/cli",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"suda": "./bin/suda.js"
|
|
@@ -28,11 +28,12 @@
|
|
|
28
28
|
"@puckeditor/core": "^0.21.3",
|
|
29
29
|
"commander": "^12.1.0",
|
|
30
30
|
"esbuild": "^0.25.12",
|
|
31
|
+
"lucide-react": "^1.17.0",
|
|
31
32
|
"open": "^10.1.0",
|
|
32
33
|
"react": "^19.2.7",
|
|
33
34
|
"react-dom": "^19.2.7",
|
|
34
35
|
"zod": "^3.24.1",
|
|
35
|
-
"@sudajs/theme-engine": "2.
|
|
36
|
+
"@sudajs/theme-engine": "2.4.0"
|
|
36
37
|
},
|
|
37
38
|
"devDependencies": {
|
|
38
39
|
"@tailwindcss/postcss": "^4.3.0",
|
|
@@ -2,42 +2,74 @@
|
|
|
2
2
|
|
|
3
3
|
This directory is a standalone Vite + React + Tailwind Suda theme generated by `suda theme init`.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Your job in a theme project is to create a complete, publishable theme with theme-specific components, realistic starter pages, scoped styling, assets, and AI metadata. Do not build pages by assembling generic engine components; those are reserved for end users who need supplemental editing blocks after the theme is installed.
|
|
6
|
+
|
|
7
|
+
## Theme contract
|
|
6
8
|
|
|
7
9
|
- Keep `renderMode: "ssr"` in `src/manifest.ts`.
|
|
8
10
|
- Keep source entries at `src/index.tsx`, `src/runtime.client.ts`, and `src/styles.css`.
|
|
11
|
+
- Export a complete `ThemeModule` from `src/index.tsx`: `manifest`, `pageConfig`, `layoutConfig`, `defaultLayout`, and `starterPages`.
|
|
12
|
+
- Keep `pageConfig` focused on page content sections. Keep `layoutConfig` focused on shared site chrome such as root, header, page outlet, and footer.
|
|
9
13
|
- Do not edit generated files under `dist/`; run `pnpm build` to regenerate them.
|
|
10
14
|
- React, React DOM, Puck, and `@sudajs/theme-engine` are host-provided peers. Do not bundle private copies into the theme runtime.
|
|
15
|
+
- Keep persisted props JSON-serializable. Do not store functions, React nodes, class instances, database ids for media, or environment-specific absolute filesystem paths.
|
|
16
|
+
|
|
17
|
+
## Component strategy
|
|
18
|
+
|
|
19
|
+
- Create theme-specific components and sections first. Every page component should express this theme's brand, industry, visual rhythm, and content model.
|
|
20
|
+
- Do not register `createEngineComponents()`, `createBaseBlocks()`, or `createContainers()` in `pageConfig.components` when creating a theme. Generic engine components are fallback editing tools for users, not the foundation of a theme template.
|
|
21
|
+
- Prefer purpose-built sections such as hero, services, product highlights, feature comparisons, gallery, process, pricing, testimonial, FAQ, article list, contact, and CTA when the theme needs those patterns.
|
|
22
|
+
- Keep section APIs semantic. Expose props that describe content and editor decisions, not implementation details.
|
|
23
|
+
- Keep `fields`, `defaultProps`, and `render` in sync. Every editor field should have a sensible default, and render functions should tolerate omitted optional values.
|
|
24
|
+
- Put editor controls in props when they change rendering, for example `showLogo`, `mediaPosition`, `tone`, or `columns`; pair dependent controls with `visibleIf` rather than hiding logic only in JSX.
|
|
25
|
+
|
|
26
|
+
## Layout and slot rules
|
|
11
27
|
|
|
12
|
-
|
|
28
|
+
- Page-level sections and components must not declare native Puck `slot` fields. Do not write `fields: { content: { type: "slot" } }` in page components.
|
|
29
|
+
- Native Puck slots are reserved for Suda-provided layout/container primitives and theme layout components. The normal template pattern is the one in `src/layout.tsx`: `rootConfig` renders `children`, `PageOutlet` renders `getPageSlot(puck?.metadata)`, and `Header` / `Footer` surround that outlet in `defaultLayout`.
|
|
30
|
+
- Do not add arbitrary nested page composition through native slots. It makes AI-generated pages unpredictable and hard to validate.
|
|
31
|
+
- If a page section needs controlled nested content, use Suda `blockSlots`, not a hand-written Puck slot field. `blockSlots` lets the theme define exactly which local block kinds are allowed inside that section.
|
|
32
|
+
- Do not use legacy DropZone or `zones` patterns.
|
|
33
|
+
|
|
34
|
+
## Styling and assets
|
|
13
35
|
|
|
14
36
|
- Tailwind v4 starts in `src/styles.css` with `@import "tailwindcss";` and `@source "./**/*.{ts,tsx}";`.
|
|
15
|
-
- Theme-local assets live in top-level `assets/`; the CLI copies them to `dist/assets/` during build.
|
|
37
|
+
- Theme-local assets live in top-level `assets/`; the CLI copies them to `dist/assets/` during build.
|
|
38
|
+
- Use `themeAsset("assets/...")` for theme-bundled assets in default props and starter pages. Do not import images from React code.
|
|
39
|
+
- Use `resolveAsset(puck?.metadata, value)` from `@sudajs/theme-engine/runtime` before rendering user-selected media fields.
|
|
40
|
+
- Scope theme CSS with the generated theme key classes. Avoid global resets that could affect the host editor or other themes.
|
|
16
41
|
- Preview screenshots are required at `assets/preview/desktop.png`, `assets/preview/tablet.png`, and `assets/preview/mobile.png`; run `suda theme capture` to generate them.
|
|
17
|
-
- Customize `vite.config.ts`, `postcss.config.mjs`, and `src/styles.css`
|
|
42
|
+
- Customize `vite.config.ts`, `postcss.config.mjs`, and `src/styles.css` only when the theme needs it.
|
|
18
43
|
|
|
19
|
-
##
|
|
44
|
+
## Field authoring rules
|
|
20
45
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
46
|
+
Use the most specific field type available. A generic `text` field may pass typecheck, but it is wrong when a semantic Suda field exists.
|
|
47
|
+
|
|
48
|
+
- Always use `url` for links and routes, including anchors, `mailto:` links, and external URLs. Do not use `text` for href-like props.
|
|
49
|
+
- Use `image` for images, `video` for videos, and `media` only when the same prop intentionally accepts mixed media.
|
|
50
|
+
- Use `color` for color values, `icon` for icon names, `font` for font selections, `range` for bounded numbers, `spacing` for spacing tokens, and `menu` for navigation items.
|
|
51
|
+
- Use `select` or `radio` for fixed choices. Keep option values stable, short, and serializable.
|
|
52
|
+
- Use `array` for repeatable items and `object` for grouped settings. Keep nested field names aligned with the prop shape and `defaultProps`.
|
|
53
|
+
- Use `textarea` for multi-sentence copy. Use `text` only for short labels, headings, slugs, names, or plain strings that are not URLs, media, colors, icons, fonts, or menus.
|
|
54
|
+
- For card grids, feature lists, pricing tables, logo walls, gallery grids, and any multi-column component, expose a `columns` prop with a `range` or `select` field and a sensible default.
|
|
55
|
+
- Use `visibleIf: ({ props }, { fields }) => boolean` for synchronous field visibility that depends on sibling props or resolved fields. Keep it pure and fast.
|
|
56
|
+
- Use Puck `resolveFields` only for heavier dynamic field changes that cannot be expressed with `visibleIf`.
|
|
57
|
+
- Add field-level `ai.instructions`, `ai.required`, `ai.exclude`, `ai.stream`, or `ai.bind` when a prop needs generation guidance beyond its label and type. Use `ai.stream: false` for atomic values such as URLs that should not stream partially.
|
|
31
58
|
|
|
32
59
|
## AI metadata
|
|
33
60
|
|
|
34
|
-
Every component and layout component
|
|
61
|
+
Every page component and layout component must declare useful `ai.instructions`. Missing component-level instructions fail `suda theme check` and block publish.
|
|
35
62
|
|
|
36
|
-
|
|
63
|
+
Good component instructions explain:
|
|
37
64
|
|
|
38
|
-
|
|
65
|
+
- Purpose: what content or business role the component serves.
|
|
66
|
+
- Use when / avoid when: how AI should choose this component instead of a similar one.
|
|
67
|
+
- Placement: where it normally belongs in a page.
|
|
68
|
+
- Frequency: whether it should appear once, multiple times, or only near another section.
|
|
69
|
+
- Composition: required neighboring content or local blocks, when relevant.
|
|
39
70
|
|
|
40
71
|
- Prefer Suda field helpers over generic text fields: use `url` for links and routes, `image` for images, `video` for videos, `media` for mixed media, `color` for colors, `icon` for icon names, `font` for font selections, `range` for bounded numbers, `spacing` for spacing tokens, and `menu` for navigation items.
|
|
72
|
+
- For icons, expose props with `{ type: "icon" }`, type values as `SudaLucideIconName`, render them with `SudaLucideIcon` from `@sudajs/theme-engine/runtime`, and store canonical Lucide names such as `"rocket"` or `"mouse-pointer-click"` (not `"lucide-rocket"`). Do not keep theme-local icon maps, emoji/icon switch statements, or custom SVG icon registries unless the theme truly needs a bespoke non-Lucide graphic.
|
|
41
73
|
- Use `select` or `radio` when authors must choose from a fixed set of values. Keep option values stable and serializable.
|
|
42
74
|
- Use `array` for repeatable items and `object` for grouped settings. Keep nested field names aligned with the prop shape and `defaultProps`.
|
|
43
75
|
- For card grids, feature lists, pricing tables, logo walls, gallery grids, and any multi-column component, expose a `columns` prop with a `range` or `select` field and a sensible default.
|
|
@@ -46,13 +78,15 @@ Use `visibleIf: ({ props }, { fields }) => boolean` for synchronous field visibi
|
|
|
46
78
|
- Put editor-only controls in props when they change rendering, for example `showLogo`; pair dependent controls with `visibleIf` rather than hiding logic only in JSX.
|
|
47
79
|
- Keep persisted props JSON-serializable. Do not store functions, React nodes, class instances, database ids for media, or environment-specific absolute filesystem paths.
|
|
48
80
|
- Add optional field-level `ai.instructions`, `ai.required`, or `ai.exclude` when a prop needs generation guidance beyond its label and type.
|
|
81
|
+
Use `ai.exclude: true` only for structural or editor-only components, and still provide instructions explaining why AI must not generate them. `PageOutlet` is the standard example.
|
|
49
82
|
|
|
50
83
|
## Block slot authoring rules
|
|
51
84
|
|
|
52
|
-
|
|
85
|
+
Use `blockSlots` when a section owns controlled local content that benefits from independent editor selection and configuration, such as hero actions, pricing cards, feature rows, stats, timeline items, or contact methods. Keep ordinary section props in `fields`.
|
|
53
86
|
|
|
54
|
-
- Type the slot prop as Puck `Slot` and declare `blockSlots` with the same prop key.
|
|
87
|
+
- Type the slot prop as Puck `Slot` and declare `blockSlots` with the same prop key. This is the Suda block-slot API; do not create a native `fields.<key>.type = "slot"` yourself.
|
|
55
88
|
- Define local block kinds under `blockSlots.<slotName>.blocks`. Local blocks support normal Suda fields, `label`, `defaultProps`, `render`, `metadata`, `inline`, `permissions`, and optional `ai`.
|
|
89
|
+
- Keep local blocks specific to the owning section. They are not reusable global page components.
|
|
56
90
|
- Do not add nested `blockSlots` or `cms` to local blocks. Block slots are one level deep in the theme authoring API.
|
|
57
91
|
- Do not put the slot prop in the host component's `defaultProps`; use `defaultBlocks` on the slot instead.
|
|
58
92
|
- Do not hand-write `id` in local block `defaultProps`, `defaultBlocks[].props`, or starter page nested block props. The editor/runtime owns block ids.
|
|
@@ -71,7 +105,8 @@ type HeroProps = {
|
|
|
71
105
|
export const Hero: SudaComponentConfig<HeroProps> = {
|
|
72
106
|
label: "Hero",
|
|
73
107
|
ai: {
|
|
74
|
-
instructions:
|
|
108
|
+
instructions:
|
|
109
|
+
"Primary page introduction for landing pages. Use once near the top with one or two local action blocks.",
|
|
75
110
|
},
|
|
76
111
|
fields: {
|
|
77
112
|
title: { type: "text", label: "Title" },
|
|
@@ -108,7 +143,7 @@ export const Hero: SudaComponentConfig<HeroProps> = {
|
|
|
108
143
|
};
|
|
109
144
|
```
|
|
110
145
|
|
|
111
|
-
Starter page data should use the public host component type and a standard
|
|
146
|
+
Starter page data should use the public host component type and a standard array on the block-slot prop. Use local block `kind` values as nested `type` values.
|
|
112
147
|
|
|
113
148
|
```ts
|
|
114
149
|
{
|
|
@@ -128,3 +163,35 @@ Starter page data should use the public host component type and a standard slot
|
|
|
128
163
|
},
|
|
129
164
|
}
|
|
130
165
|
```
|
|
166
|
+
|
|
167
|
+
## Starter pages
|
|
168
|
+
|
|
169
|
+
- Provide realistic starter pages in `src/templates.ts`; do not ship placeholder-only pages.
|
|
170
|
+
- Every starter page is Puck `Data` with `root: { props: {} }` and `content: [...]`.
|
|
171
|
+
- Each top-level component instance in starter page `content` must include a stable `props.id`.
|
|
172
|
+
- Use only components registered in `pageConfig.components` for top-level page content.
|
|
173
|
+
- Match starter page content to the theme's intended audience and category. The home page should show the theme's best composition, not just every component in order.
|
|
174
|
+
- Use `themeAsset("assets/...")` for bundled starter media.
|
|
175
|
+
|
|
176
|
+
## CMS-aware themes
|
|
177
|
+
|
|
178
|
+
- If the theme supports posts or tags, declare `content.views` on the exported `ThemeModule`.
|
|
179
|
+
- Add one starter page with matching `cmsView` for every declared CMS view.
|
|
180
|
+
- Declare component-level `cms` capabilities only on components that can render that CMS source.
|
|
181
|
+
- Use `cmsBinding` in starter page props when a component should receive CMS data at render time.
|
|
182
|
+
- Keep static fallback props for CMS components so the page still renders meaningfully when CMS payloads are absent.
|
|
183
|
+
|
|
184
|
+
## Commands
|
|
185
|
+
|
|
186
|
+
```bash
|
|
187
|
+
pnpm install
|
|
188
|
+
pnpm lint
|
|
189
|
+
pnpm typecheck
|
|
190
|
+
pnpm format
|
|
191
|
+
pnpm dev # Vite-powered local preview
|
|
192
|
+
pnpm build # Vite build + Suda artifact finalize + AI metadata check
|
|
193
|
+
pnpm validate
|
|
194
|
+
suda theme check
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
Before publishing or handing off a finished theme, run `pnpm typecheck`, `pnpm lint`, `pnpm build`, and `pnpm validate`. Use `suda theme check` when iterating on AI metadata.
|
|
@@ -31,6 +31,7 @@
|
|
|
31
31
|
"eslint-plugin-react-hooks": "^7.1.1",
|
|
32
32
|
"eslint-plugin-react-refresh": "^0.4.26",
|
|
33
33
|
"globals": "^16.5.0",
|
|
34
|
+
"lucide-react": "^1.17.0",
|
|
34
35
|
"postcss": "^8.5.15",
|
|
35
36
|
"prettier": "^3.8.4",
|
|
36
37
|
"react": "^19.2.7",
|
|
@@ -43,6 +44,7 @@
|
|
|
43
44
|
"peerDependencies": {
|
|
44
45
|
"@puckeditor/core": "^0.21.3",
|
|
45
46
|
"@sudajs/theme-engine": "*",
|
|
47
|
+
"lucide-react": "^1.17.0",
|
|
46
48
|
"react": "^19.2.7",
|
|
47
49
|
"react-dom": "^19.2.7"
|
|
48
50
|
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import type { SudaComponentConfig } from "@sudajs/theme-engine";
|
|
2
|
-
import { resolveAsset, type ThemeRenderMetadata } from "@sudajs/theme-engine/runtime";
|
|
1
|
+
import type { SudaComponentConfig, SudaLucideIconName } from "@sudajs/theme-engine";
|
|
2
|
+
import { resolveAsset, SudaLucideIcon, type ThemeRenderMetadata } from "@sudajs/theme-engine/runtime";
|
|
3
3
|
|
|
4
4
|
import { themeAsset } from "./theme-asset.js";
|
|
5
5
|
|
|
6
|
-
type FeatureItem = { title?: string; description?: string };
|
|
6
|
+
type FeatureItem = { icon?: SudaLucideIconName; title?: string; description?: string };
|
|
7
7
|
type PuckExtras = { puck?: { metadata?: ThemeRenderMetadata } };
|
|
8
8
|
type HeroProps = {
|
|
9
9
|
showLogo?: boolean;
|
|
@@ -114,6 +114,7 @@ export const FeatureGrid: SudaComponentConfig<FeatureGridProps> = {
|
|
|
114
114
|
label: "Features",
|
|
115
115
|
arrayFields: {
|
|
116
116
|
title: { type: "text", label: "Title" },
|
|
117
|
+
icon: { type: "icon", label: "Icon" },
|
|
117
118
|
description: { type: "textarea", label: "Description" },
|
|
118
119
|
},
|
|
119
120
|
},
|
|
@@ -123,9 +124,9 @@ export const FeatureGrid: SudaComponentConfig<FeatureGridProps> = {
|
|
|
123
124
|
description: "Use this section to explain the core value of the project.",
|
|
124
125
|
columns: 3,
|
|
125
126
|
features: [
|
|
126
|
-
{ title: "Fast setup", description: "Start from a clean theme contract." },
|
|
127
|
-
{ title: "Visual editing", description: "Expose content fields through Puck." },
|
|
128
|
-
{ title: "Publish ready", description: "Build and publish with the Suda CLI." },
|
|
127
|
+
{ icon: "rocket", title: "Fast setup", description: "Start from a clean theme contract." },
|
|
128
|
+
{ icon: "mouse-pointer-click", title: "Visual editing", description: "Expose content fields through Puck." },
|
|
129
|
+
{ icon: "send", title: "Publish ready", description: "Build and publish with the Suda CLI." },
|
|
129
130
|
],
|
|
130
131
|
},
|
|
131
132
|
render: ({ title, description, columns = 3, features = [] }) => (
|
|
@@ -138,6 +139,7 @@ export const FeatureGrid: SudaComponentConfig<FeatureGridProps> = {
|
|
|
138
139
|
>
|
|
139
140
|
{features.map((feature: FeatureItem, index: number) => (
|
|
140
141
|
<article className="__SUDA_THEME_KEY__-card" key={index}>
|
|
142
|
+
<SudaLucideIcon className="__SUDA_THEME_KEY__-card-icon" name={feature.icon} />
|
|
141
143
|
<h3>{feature.title}</h3>
|
|
142
144
|
<p>{feature.description}</p>
|
|
143
145
|
</article>
|