@sudajs/cli 0.7.0 → 0.7.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.7.0",
3
+ "version": "0.7.2",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "suda": "./bin/suda.js"
@@ -25,14 +25,14 @@
25
25
  },
26
26
  "dependencies": {
27
27
  "@modelcontextprotocol/sdk": "^1.29.0",
28
- "@puckeditor/core": "^0.21.2",
28
+ "@puckeditor/core": "^0.21.3",
29
29
  "commander": "^12.1.0",
30
30
  "esbuild": "^0.25.12",
31
31
  "open": "^10.1.0",
32
- "react": "^19.0.0",
33
- "react-dom": "^19.0.0",
32
+ "react": "^19.2.7",
33
+ "react-dom": "^19.2.7",
34
34
  "zod": "^3.24.1",
35
- "@sudajs/theme-engine": "2.0.2"
35
+ "@sudajs/theme-engine": "2.0.3"
36
36
  },
37
37
  "devDependencies": {
38
38
  "@tailwindcss/postcss": "^4.3.0",
@@ -0,0 +1,7 @@
1
+ {
2
+ "semi": true,
3
+ "singleQuote": false,
4
+ "trailingComma": "all",
5
+ "printWidth": 100,
6
+ "tabWidth": 2
7
+ }
@@ -12,13 +12,16 @@ This directory is a standalone Vite + React + Tailwind Suda theme generated by `
12
12
  ## Styling
13
13
 
14
14
  - Tailwind v4 starts in `src/styles.css` with `@import "tailwindcss";` and `@source "./**/*.{ts,tsx}";`.
15
+ - Theme-local assets live in `src/assets/`; the CLI copies them to `dist/assets/` during build.
15
16
  - Customize `vite.config.ts`, `postcss.config.mjs`, and `src/styles.css` as needed.
16
17
 
17
18
  ## Commands
18
19
 
19
20
  ```bash
20
21
  pnpm install
22
+ pnpm lint
21
23
  pnpm typecheck
24
+ pnpm format
22
25
  pnpm dev # Vite-powered local preview
23
26
  pnpm build # Vite build + Suda artifact finalize + AI metadata check
24
27
  pnpm validate
@@ -12,13 +12,17 @@ pnpm install
12
12
 
13
13
  ```bash
14
14
  pnpm dev
15
+ pnpm lint
15
16
  pnpm typecheck
17
+ pnpm format
16
18
  pnpm build
17
19
  pnpm validate
18
20
  ```
19
21
 
20
22
  `pnpm dev` starts the Vite-powered preview. `pnpm build` produces the Suda artifact contract in `dist/`, including `dist/index.js`, `dist/runtime.client.js`, `dist/manifest.json`, and `dist/styles.css`.
21
23
 
24
+ Theme-local assets live in `src/assets/`. The starter hero exposes a Logo field that defaults to `src/assets/suda-logo.svg`, so you can replace it with your own brand image or choose a different image in the editor.
25
+
22
26
  ## Publish
23
27
 
24
28
  ```bash
@@ -0,0 +1,48 @@
1
+ import js from "@eslint/js";
2
+ import reactHooks from "eslint-plugin-react-hooks";
3
+ import reactRefresh from "eslint-plugin-react-refresh";
4
+ import globals from "globals";
5
+ import prettier from "eslint-config-prettier";
6
+ import tseslint from "typescript-eslint";
7
+
8
+ export default tseslint.config(
9
+ {
10
+ ignores: ["dist/**", ".suda-build/**", "node_modules/**", "*.tsbuildinfo"],
11
+ },
12
+ js.configs.recommended,
13
+ {
14
+ files: ["**/*.{js,mjs,cjs}"],
15
+ languageOptions: {
16
+ ecmaVersion: 2022,
17
+ globals: globals.node,
18
+ },
19
+ },
20
+ ...tseslint.configs.recommendedTypeChecked.map((config) => ({
21
+ ...config,
22
+ files: ["**/*.{ts,tsx}"],
23
+ })),
24
+ {
25
+ files: ["**/*.{ts,tsx}"],
26
+ languageOptions: {
27
+ ecmaVersion: 2022,
28
+ globals: globals.browser,
29
+ parserOptions: {
30
+ projectService: true,
31
+ tsconfigRootDir: import.meta.dirname,
32
+ },
33
+ },
34
+ plugins: {
35
+ "react-hooks": reactHooks,
36
+ "react-refresh": reactRefresh,
37
+ },
38
+ rules: {
39
+ ...reactHooks.configs.recommended.rules,
40
+ "@typescript-eslint/consistent-type-imports": ["error", { prefer: "type-imports" }],
41
+ "react-refresh/only-export-components": [
42
+ "warn",
43
+ { allowConstantExport: true },
44
+ ],
45
+ },
46
+ },
47
+ prettier,
48
+ );
@@ -10,29 +10,40 @@
10
10
  "build": "suda theme build --theme-root .",
11
11
  "dev": "suda theme dev --theme-root .",
12
12
  "finalize": "suda theme finalize --theme-root .",
13
- "typecheck": "tsc -p tsconfig.json --noEmit",
13
+ "format": "prettier --write .",
14
+ "lint": "eslint .",
15
+ "lint:fix": "eslint . --fix",
16
+ "typecheck": "tsc -p tsconfig.json",
14
17
  "validate": "suda theme validate --theme-root ."
15
18
  },
16
19
  "devDependencies": {
20
+ "@eslint/js": "^9.39.4",
17
21
  "@tailwindcss/postcss": "^4.3.0",
18
- "@puckeditor/core": "^0.21.2",
22
+ "@puckeditor/core": "^0.21.3",
19
23
  "@sudajs/cli": "*",
20
24
  "@sudajs/theme-engine": "*",
21
- "@types/node": "^22.0.0",
22
- "@types/react": "^19.0.0",
23
- "@types/react-dom": "^19.0.0",
24
- "@vitejs/plugin-react": "^5.1.2",
25
+ "@types/node": "^24.13.2",
26
+ "@types/react": "^19.2.17",
27
+ "@types/react-dom": "^19.2.3",
28
+ "@vitejs/plugin-react": "^6.0.2",
29
+ "eslint": "^9.39.4",
30
+ "eslint-config-prettier": "^9.1.2",
31
+ "eslint-plugin-react-hooks": "^7.1.1",
32
+ "eslint-plugin-react-refresh": "^0.4.26",
33
+ "globals": "^16.5.0",
25
34
  "postcss": "^8.5.15",
26
- "react": "^19.0.0",
27
- "react-dom": "^19.0.0",
35
+ "prettier": "^3.8.4",
36
+ "react": "^19.2.7",
37
+ "react-dom": "^19.2.7",
28
38
  "tailwindcss": "^4.3.0",
29
- "typescript": "^5.6.0",
30
- "vite": "^7.3.3"
39
+ "typescript": "~6.0.2",
40
+ "typescript-eslint": "^8.62.0",
41
+ "vite": "^8.1.0"
31
42
  },
32
43
  "peerDependencies": {
33
- "@puckeditor/core": "^0.21.2",
44
+ "@puckeditor/core": "^0.21.3",
34
45
  "@sudajs/theme-engine": "*",
35
- "react": "^19.0.0",
36
- "react-dom": "^19.0.0"
46
+ "react": "^19.2.7",
47
+ "react-dom": "^19.2.7"
37
48
  }
38
49
  }
@@ -0,0 +1,17 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <svg xmlns="http://www.w3.org/2000/svg" width="500" height="500" viewBox="0 0 2048 2048">
3
+ <defs>
4
+ <linearGradient id="bg" x1="0" y1="0" x2="1" y2="1">
5
+ <stop offset="0%" stop-color="#00B6F6" />
6
+ <stop offset="100%" stop-color="#0538A1" />
7
+ </linearGradient>
8
+ </defs>
9
+
10
+ <rect x="0" y="0" width="2048" height="2048" fill="url(#bg)" />
11
+
12
+ <polygon fill="#FFFFFF" points="1508,503 989,503 934,710 867,764 398,764 328,1022 847,1022 902,816 969,761 1439,761" />
13
+
14
+ <polygon fill="#FFFFFF" points="929,894 860,1153 1118,1153 1187,894" />
15
+
16
+ <polygon fill="#FFFFFF" points="1719,1025 1200,1024 1144,1233 1079,1285 608,1285 539,1544 1058,1544 1114,1335 1179,1283 1650,1283" />
17
+ </svg>
@@ -1,19 +1,19 @@
1
- import type { Config, Data } from "@puckeditor/core";
1
+ import type { LayoutData, SudaConfig } from "@sudajs/theme-engine";
2
2
 
3
3
  import { LAYOUT_COMPONENTS, rootConfig } from "./layout.js";
4
4
  import { SECTION_COMPONENTS } from "./sections.js";
5
5
 
6
- export const pageConfig: Config = {
6
+ export const pageConfig: SudaConfig = {
7
7
  components: SECTION_COMPONENTS,
8
8
  };
9
9
 
10
- export const layoutConfig: Config = {
10
+ export const layoutConfig: SudaConfig = {
11
11
  root: rootConfig,
12
12
  components: LAYOUT_COMPONENTS,
13
13
  };
14
14
 
15
- export const defaultLayout: Data = {
16
- root: { props: { siteName: "__SUDA_THEME_KEY__" } },
15
+ export const defaultLayout: LayoutData = {
16
+ root: { props: { title: "__SUDA_THEME_KEY__" } },
17
17
  content: [
18
18
  { type: "Header", props: { id: "Header-1", siteName: "__SUDA_THEME_KEY__" } },
19
19
  { type: "PageOutlet", props: { id: "PageOutlet-1" } },
@@ -1,21 +1,26 @@
1
- import type { Config } from "@puckeditor/core";
2
- import type { SudaComponentConfig } from "@sudajs/theme-engine";
1
+ import type { SudaComponentConfig, SudaRootConfig } from "@sudajs/theme-engine";
3
2
  import { getIcpRecord, getPageSlot, getWhiteLabel } from "@sudajs/theme-engine/runtime";
4
3
  import type { ReactElement, ReactNode } from "react";
5
4
 
6
5
  type PuckExtras = { puck?: { metadata?: Record<string, unknown> } };
6
+ type RootProps = { children?: ReactNode; title?: string };
7
+ type HeaderProps = { siteName?: string };
8
+ type PageOutletProps = PuckExtras;
9
+ type FooterProps = { text?: string } & PuckExtras;
7
10
 
8
- export const rootConfig: NonNullable<Config["root"]> = {
11
+ export const rootConfig: SudaRootConfig<RootProps> = {
9
12
  fields: {
10
- siteName: { type: "text", label: "Site name" },
13
+ title: { type: "text", label: "Site title" },
11
14
  },
12
- defaultProps: { siteName: "__SUDA_THEME_KEY__" },
13
- render: ({ children, siteName }: { children?: ReactNode; siteName?: string }) => (
14
- <div className="__SUDA_THEME_KEY__-root" data-site-name={siteName}>{children}</div>
15
+ defaultProps: { title: "__SUDA_THEME_KEY__" },
16
+ render: ({ children, title }) => (
17
+ <div className="__SUDA_THEME_KEY__-root" data-site-name={title}>
18
+ {children}
19
+ </div>
15
20
  ),
16
21
  };
17
22
 
18
- export const Header: SudaComponentConfig = {
23
+ export const Header: SudaComponentConfig<HeaderProps> = {
19
24
  label: "Header",
20
25
  ai: {
21
26
  instructions:
@@ -28,7 +33,7 @@ export const Header: SudaComponentConfig = {
28
33
  render: ({ siteName }) => <header className="__SUDA_THEME_KEY__-header">{siteName}</header>,
29
34
  };
30
35
 
31
- export const PageOutlet: SudaComponentConfig = {
36
+ export const PageOutlet: SudaComponentConfig<PageOutletProps> = {
32
37
  label: "Page outlet",
33
38
  ai: {
34
39
  exclude: true,
@@ -38,10 +43,10 @@ export const PageOutlet: SudaComponentConfig = {
38
43
  },
39
44
  fields: {},
40
45
  defaultProps: {},
41
- render: (props: PuckExtras): ReactElement => <>{getPageSlot(props.puck?.metadata)}</>,
46
+ render: (props): ReactElement => <>{getPageSlot(props.puck?.metadata)}</>,
42
47
  };
43
48
 
44
- export const Footer: SudaComponentConfig = {
49
+ export const Footer: SudaComponentConfig<FooterProps> = {
45
50
  label: "Footer",
46
51
  ai: {
47
52
  instructions:
@@ -51,7 +56,7 @@ export const Footer: SudaComponentConfig = {
51
56
  },
52
57
  fields: { text: { type: "text", label: "Text" } },
53
58
  defaultProps: { text: "© __SUDA_THEME_KEY__" },
54
- render: ({ text, puck }: { text?: string } & PuckExtras) => {
59
+ render: ({ text, puck }) => {
55
60
  const whiteLabel = getWhiteLabel(puck?.metadata);
56
61
  const icp = getIcpRecord(puck?.metadata);
57
62
  return (
@@ -1,13 +1,18 @@
1
1
  import type { SudaComponentConfig } from "@sudajs/theme-engine";
2
+ import { resolveAsset, type ThemeRenderMetadata } from "@sudajs/theme-engine/runtime";
3
+
4
+ import { themeAsset } from "./theme-asset.js";
2
5
 
3
6
  type FeatureItem = { title?: string; description?: string };
7
+ type PuckExtras = { puck?: { metadata?: ThemeRenderMetadata } };
4
8
  type HeroProps = {
9
+ logo?: string;
5
10
  eyebrow?: string;
6
11
  title?: string;
7
12
  description?: string;
8
13
  primaryLabel?: string;
9
14
  primaryHref?: string;
10
- };
15
+ } & PuckExtras;
11
16
  type FeatureGridProps = {
12
17
  title?: string;
13
18
  description?: string;
@@ -25,7 +30,7 @@ type CallToActionProps = {
25
30
  buttonHref?: string;
26
31
  };
27
32
 
28
- export const Hero: SudaComponentConfig = {
33
+ export const Hero: SudaComponentConfig<HeroProps> = {
29
34
  label: "Hero",
30
35
  ai: {
31
36
  instructions:
@@ -35,6 +40,7 @@ export const Hero: SudaComponentConfig = {
35
40
  "Use at most once per page. Do not use for ordinary section headings or article content.",
36
41
  },
37
42
  fields: {
43
+ logo: { type: "image", label: "Logo" },
38
44
  eyebrow: { type: "text", label: "Eyebrow" },
39
45
  title: { type: "text", label: "Title" },
40
46
  description: { type: "textarea", label: "Description" },
@@ -42,23 +48,32 @@ export const Hero: SudaComponentConfig = {
42
48
  primaryHref: { type: "text", label: "Primary button link" },
43
49
  },
44
50
  defaultProps: {
51
+ logo: themeAsset("assets/suda-logo.svg"),
45
52
  eyebrow: "New theme",
46
53
  title: "Build with SudaCloud",
47
54
  description: "Edit this starter section in the visual editor.",
48
55
  primaryLabel: "Get started",
49
56
  primaryHref: "#contact",
50
57
  },
51
- render: ({ eyebrow, title, description, primaryLabel, primaryHref }: HeroProps) => (
52
- <section className="__SUDA_THEME_KEY__-section __SUDA_THEME_KEY__-hero">
53
- <p className="__SUDA_THEME_KEY__-eyebrow">{eyebrow}</p>
54
- <h1>{title}</h1>
55
- <p>{description}</p>
56
- <a className="__SUDA_THEME_KEY__-button" href={primaryHref}>{primaryLabel}</a>
57
- </section>
58
- ),
58
+ render: ({ logo, eyebrow, title, description, primaryLabel, primaryHref, puck }) => {
59
+ const logoSrc = resolveAsset(puck?.metadata, logo);
60
+ return (
61
+ <section className="__SUDA_THEME_KEY__-section __SUDA_THEME_KEY__-hero">
62
+ {logoSrc ? (
63
+ <img className="__SUDA_THEME_KEY__-hero-logo" src={logoSrc} alt="" />
64
+ ) : null}
65
+ <p className="__SUDA_THEME_KEY__-eyebrow">{eyebrow}</p>
66
+ <h1>{title}</h1>
67
+ <p>{description}</p>
68
+ <a className="__SUDA_THEME_KEY__-button" href={primaryHref}>
69
+ {primaryLabel}
70
+ </a>
71
+ </section>
72
+ );
73
+ },
59
74
  };
60
75
 
61
- export const FeatureGrid: SudaComponentConfig = {
76
+ export const FeatureGrid: SudaComponentConfig<FeatureGridProps> = {
62
77
  label: "Feature grid",
63
78
  ai: {
64
79
  instructions:
@@ -87,7 +102,7 @@ export const FeatureGrid: SudaComponentConfig = {
87
102
  { title: "Publish ready", description: "Build and publish with the Suda CLI." },
88
103
  ],
89
104
  },
90
- render: ({ title, description, features = [] }: FeatureGridProps) => (
105
+ render: ({ title, description, features = [] }) => (
91
106
  <section className="__SUDA_THEME_KEY__-section">
92
107
  <h2>{title}</h2>
93
108
  <p>{description}</p>
@@ -103,7 +118,7 @@ export const FeatureGrid: SudaComponentConfig = {
103
118
  ),
104
119
  };
105
120
 
106
- export const Testimonial: SudaComponentConfig = {
121
+ export const Testimonial: SudaComponentConfig<TestimonialProps> = {
107
122
  label: "Testimonial",
108
123
  ai: {
109
124
  instructions:
@@ -121,15 +136,17 @@ export const Testimonial: SudaComponentConfig = {
121
136
  author: "Alex Chen",
122
137
  role: "Founder",
123
138
  },
124
- render: ({ quote, author, role }: TestimonialProps) => (
139
+ render: ({ quote, author, role }) => (
125
140
  <section className="__SUDA_THEME_KEY__-section __SUDA_THEME_KEY__-quote">
126
141
  <blockquote>{quote}</blockquote>
127
- <p>{author} · {role}</p>
142
+ <p>
143
+ {author} · {role}
144
+ </p>
128
145
  </section>
129
146
  ),
130
147
  };
131
148
 
132
- export const CallToAction: SudaComponentConfig = {
149
+ export const CallToAction: SudaComponentConfig<CallToActionProps> = {
133
150
  label: "Call to action",
134
151
  ai: {
135
152
  instructions:
@@ -149,11 +166,13 @@ export const CallToAction: SudaComponentConfig = {
149
166
  buttonLabel: "Contact us",
150
167
  buttonHref: "#contact",
151
168
  },
152
- render: ({ title, description, buttonLabel, buttonHref }: CallToActionProps) => (
169
+ render: ({ title, description, buttonLabel, buttonHref }) => (
153
170
  <section className="__SUDA_THEME_KEY__-section __SUDA_THEME_KEY__-cta" id="contact">
154
171
  <h2>{title}</h2>
155
172
  <p>{description}</p>
156
- <a className="__SUDA_THEME_KEY__-button" href={buttonHref}>{buttonLabel}</a>
173
+ <a className="__SUDA_THEME_KEY__-button" href={buttonHref}>
174
+ {buttonLabel}
175
+ </a>
157
176
  </section>
158
177
  ),
159
178
  };
@@ -37,6 +37,12 @@
37
37
  .__SUDA_THEME_KEY__-hero {
38
38
  background: #f8fafc;
39
39
  }
40
+ .__SUDA_THEME_KEY__-hero-logo {
41
+ width: 64px;
42
+ height: 64px;
43
+ border-radius: 14px;
44
+ margin-bottom: 24px;
45
+ }
40
46
  .__SUDA_THEME_KEY__-eyebrow {
41
47
  text-transform: uppercase;
42
48
  letter-spacing: 0.08em;
@@ -1,5 +1,7 @@
1
1
  import type { ThemeStarterPage } from "@sudajs/theme-engine";
2
2
 
3
+ import { themeAsset } from "./theme-asset.js";
4
+
3
5
  export const starterPages: ThemeStarterPage[] = [
4
6
  {
5
7
  slug: "home",
@@ -12,6 +14,7 @@ export const starterPages: ThemeStarterPage[] = [
12
14
  type: "Hero",
13
15
  props: {
14
16
  id: "Hero-1",
17
+ logo: themeAsset("assets/suda-logo.svg"),
15
18
  eyebrow: "Starter page",
16
19
  title: "Welcome to __SUDA_THEME_KEY__",
17
20
  description: "This page was generated by suda theme init.",
@@ -0,0 +1,3 @@
1
+ /// <reference types="vite/client" />
2
+
3
+ declare const __SUDA_THEME_VERSION__: string;
@@ -1,22 +1,27 @@
1
1
  {
2
2
  "compilerOptions": {
3
- "target": "ES2022",
4
- "lib": ["DOM", "DOM.Iterable", "ES2022"],
3
+ "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.tsbuildinfo",
4
+ "target": "ES2023",
5
+ "lib": ["ES2023", "DOM", "DOM.Iterable"],
5
6
  "module": "NodeNext",
6
7
  "moduleResolution": "NodeNext",
8
+ "types": ["node", "vite/client"],
9
+ "skipLibCheck": true,
10
+
11
+ "allowImportingTsExtensions": true,
12
+ "verbatimModuleSyntax": true,
13
+ "moduleDetection": "force",
14
+ "noEmit": true,
7
15
  "jsx": "react-jsx",
8
- "rootDir": "src",
9
- "outDir": "dist",
10
- "declaration": true,
11
- "declarationMap": true,
12
- "sourceMap": true,
16
+ "resolveJsonModule": true,
17
+
13
18
  "strict": true,
14
19
  "noUncheckedIndexedAccess": true,
15
- "esModuleInterop": true,
16
- "skipLibCheck": true,
17
- "forceConsistentCasingInFileNames": true,
18
- "resolveJsonModule": true,
19
- "types": ["node"]
20
+ "exactOptionalPropertyTypes": true,
21
+ "noUnusedLocals": true,
22
+ "noUnusedParameters": true,
23
+ "erasableSyntaxOnly": true,
24
+ "noFallthroughCasesInSwitch": true
20
25
  },
21
- "include": ["src", "package.json"]
26
+ "include": ["src", "vite.config.ts", "postcss.config.mjs", "eslint.config.js"]
22
27
  }
@@ -2,9 +2,27 @@ import react from "@vitejs/plugin-react";
2
2
  import { defineConfig } from "vite";
3
3
  import pkg from "./package.json" with { type: "json" };
4
4
 
5
+ function assetFileNames(assetInfo: { names?: string[] | undefined }): string {
6
+ const names = assetInfo.names ?? [];
7
+ const isCss = names.some((name) => name.endsWith(".css"));
8
+ if (isCss) {
9
+ return "styles.css";
10
+ }
11
+ return "assets/[name]-[hash][extname]";
12
+ }
13
+
5
14
  export default defineConfig({
6
15
  define: {
7
16
  __SUDA_THEME_VERSION__: JSON.stringify(pkg.version),
8
17
  },
9
18
  plugins: [react()],
19
+ build: {
20
+ assetsInlineLimit: 0,
21
+ cssCodeSplit: false,
22
+ rollupOptions: {
23
+ output: {
24
+ assetFileNames,
25
+ },
26
+ },
27
+ },
10
28
  });