@sudajs/cli 0.13.4 → 0.15.0

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.13.4",
3
+ "version": "0.15.0",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "suda": "./bin/suda.js"
@@ -34,7 +34,7 @@
34
34
  "react": "^19.2.7",
35
35
  "react-dom": "^19.2.7",
36
36
  "zod": "^3.24.1",
37
- "@sudajs/theme-engine": "5.1.4"
37
+ "@sudajs/theme-engine": "6.0.0"
38
38
  },
39
39
  "devDependencies": {
40
40
  "@tailwindcss/postcss": "^4.3.0",
@@ -439,7 +439,8 @@ or AI examples.
439
439
  ## Theme contract
440
440
 
441
441
  - Keep `renderMode: "ssr"` in `src/manifest.ts`.
442
- - Keep source entries at `src/index.tsx`, `src/runtime.client.ts`, and `src/styles.css`.
442
+ - Keep source entries at `src/index.tsx` and `src/styles.css`. The CLI owns and generates `dist/runtime.client.js`; do not add `src/runtime.client.ts(x)`.
443
+ - If the theme needs browser-only setup, export `clientHooks` from `src/client.ts`. Do not export theme configuration from that file.
443
444
  - Export a complete `ThemeModule` from `src/index.tsx`: `manifest`, `pageConfig`, `layoutConfig`, `defaultLayout`, `starterPages`, and `cmsTemplates`.
444
445
  - Starter page slugs preview at site-like root routes in `suda theme dev`: `index` is `/index`, `contact-us` is `/contact-us`. Do not use `/pages/...` as a preview route prefix.
445
446
  - Keep `pageConfig` focused on page content sections. Keep `layoutConfig` focused on shared site chrome such as root, header, page outlet, and footer.
@@ -597,14 +598,33 @@ other editor-facing configuration string.
597
598
  - When adding another locale such as `src/locales/zh-CN.json`, keep its leaf key
598
599
  set exactly aligned with `src/locales/en.json`; missing or extra keys fail
599
600
  `suda theme validate`, `suda theme check`, `pnpm build`, and publish.
600
- - The CLI copies `src/locales/*.json` into `dist/locales/` during build. Do not
601
+ - The CLI copies `src/locales/*.json` into `dist/locales/` during build. Files
602
+ named `<locale>.json` are editor messages; files named
603
+ `<locale>.content.json` are starter preview content messages. Do not
601
604
  edit `dist/locales/` directly.
602
605
  - If an installed legacy theme is missing locale files, the editor falls back to
603
606
  showing translation keys instead of crashing. New themes must still pass
604
607
  locale validation before publishing.
605
- - Do not put actual page content, starter page copy, default prop values, or
606
- public-site text through `t(...)`. Those belong in component props and starter
607
- page data so users can edit them.
608
+ - Keep editor `t(...)` out of actual page content, starter page copy, default
609
+ prop values, and public-site text. Starter preview copy may instead use the
610
+ separate `t` exported by `src/preview-i18n.ts`; direct strings remain valid
611
+ and editor-saved values are always plain strings.
612
+
613
+ ## Starter preview content locales
614
+
615
+ Use `src/preview-i18n.ts` only inside starter page templates for user-visible
616
+ content such as page titles, headings, descriptions, list copy, and button
617
+ labels. Keep URLs, slugs, IDs, component types, and asset paths as direct
618
+ strings. Do not use preview translations in component labels, fields, default
619
+ props, layout configuration, or CMS templates.
620
+
621
+ - `src/locales/en.content.json` is the required default content locale whenever
622
+ `manifest.previewLocales` is declared.
623
+ - Add every supported locale to `manifest.previewLocales`, including `en`, and
624
+ create the matching `src/locales/<locale>.content.json` file.
625
+ - Every content locale file must have exactly the same string leaf keys as
626
+ `en.content.json`; undeclared, missing, extra, or partial locale files fail
627
+ validation and publishing.
608
628
 
609
629
  Example:
610
630
 
@@ -19,11 +19,11 @@ pnpm build
19
19
  pnpm validate
20
20
  ```
21
21
 
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`. Published artifact paths omit the local `dist/` prefix.
22
+ `pnpm dev` starts the Vite-powered preview. `pnpm build` produces the Suda artifact contract in `dist/`, including the CLI-owned `dist/runtime.client.js`, `dist/index.js`, `dist/manifest.json`, and `dist/styles.css`. Published artifact paths omit the local `dist/` prefix. Do not add `src/runtime.client.ts(x)`; optional browser setup belongs in a `clientHooks` export from `src/client.ts`.
23
23
 
24
24
  Theme-local assets live in top-level `assets/`. The starter hero exposes a Logo field that defaults to `assets/brand/suda-logo.svg`, so you can replace it with your own brand image or choose a different image in the editor.
25
25
 
26
- Editor labels are localized through `src/i18n.ts` and `src/locales/*.json`. Use `t("...")` for component labels, field labels, and option labels, then add matching string leaves to `src/locales/en.json`. `en.json` is required, and any additional locale file must have the same leaf key set. `pnpm validate`, `suda theme check`, and `pnpm build` verify the locale contract.
26
+ Editor labels are localized through `src/i18n.ts` and `src/locales/<locale>.json`. Starter preview content uses the separate `src/preview-i18n.ts` helper and `src/locales/<locale>.content.json`. Keep `previewLocales` in the manifest aligned with the content locale files. `pnpm validate`, `suda theme check`, and `pnpm build` verify both locale contracts.
27
27
 
28
28
  Preview screenshots are required before publishing:
29
29
 
@@ -0,0 +1,94 @@
1
+ {
2
+ "pages": {
3
+ "index": {
4
+ "title": "Home",
5
+ "hero": {
6
+ "eyebrow": "Starter page",
7
+ "title": "Welcome to __SUDA_THEME_KEY__",
8
+ "description": "This page was generated by suda theme init.",
9
+ "primaryLabel": "Explore features"
10
+ },
11
+ "features": {
12
+ "title": "Designed for editable sites",
13
+ "description": "Starter sections show agents and editors how this theme is structured.",
14
+ "items": {
15
+ "typedFields": {
16
+ "title": "Typed fields",
17
+ "description": "Each section exposes a clear field schema."
18
+ },
19
+ "starterPages": {
20
+ "title": "Starter pages",
21
+ "description": "Templates show realistic section composition."
22
+ },
23
+ "cliWorkflow": {
24
+ "title": "CLI workflow",
25
+ "description": "Build, validate, preview, and publish from one tool."
26
+ }
27
+ }
28
+ },
29
+ "featuredPosts": {
30
+ "title": "Featured posts",
31
+ "description": "Use CMS posts to keep the homepage fresh without manual card editing."
32
+ },
33
+ "callToAction": {
34
+ "title": "Launch your first page",
35
+ "description": "Customize this starter template or let an agent generate a new draft.",
36
+ "buttonLabel": "Get in touch"
37
+ }
38
+ },
39
+ "services": {
40
+ "title": "Services",
41
+ "hero": {
42
+ "eyebrow": "Services",
43
+ "title": "Show what you offer",
44
+ "description": "Use this page to explain core services, packages, or capabilities.",
45
+ "primaryLabel": "Contact us"
46
+ },
47
+ "features": {
48
+ "title": "Service highlights",
49
+ "description": "Replace these cards with the most important ways you help customers."
50
+ }
51
+ },
52
+ "team": {
53
+ "title": "Team",
54
+ "hero": {
55
+ "eyebrow": "Team",
56
+ "title": "Introduce the people behind the work",
57
+ "description": "Share experience, roles, and the human story behind the project.",
58
+ "primaryLabel": "Work with us"
59
+ },
60
+ "testimonial": {
61
+ "quote": "Use this block for a founder note, client quote, or team philosophy.",
62
+ "author": "Team lead",
63
+ "role": "Founder"
64
+ }
65
+ },
66
+ "about": {
67
+ "title": "About",
68
+ "hero": {
69
+ "eyebrow": "About",
70
+ "title": "A clean starting point for your story",
71
+ "description": "Use this page to introduce the project, audience, and promise.",
72
+ "primaryLabel": "Contact us"
73
+ },
74
+ "testimonial": {
75
+ "quote": "This starter theme keeps the editable surface focused and predictable.",
76
+ "role": "Theme team"
77
+ }
78
+ },
79
+ "contact": {
80
+ "title": "Contact",
81
+ "hero": {
82
+ "eyebrow": "Contact",
83
+ "title": "Let's talk",
84
+ "description": "Tell visitors how to reach you and what happens next.",
85
+ "primaryLabel": "Email us"
86
+ },
87
+ "callToAction": {
88
+ "title": "Start the conversation",
89
+ "description": "Replace this copy with your preferred contact details or form link.",
90
+ "buttonLabel": "Send an email"
91
+ }
92
+ }
93
+ }
94
+ }
@@ -0,0 +1,94 @@
1
+ {
2
+ "pages": {
3
+ "index": {
4
+ "title": "首页",
5
+ "hero": {
6
+ "eyebrow": "起始页面",
7
+ "title": "欢迎使用 __SUDA_THEME_KEY__",
8
+ "description": "此页面由 suda theme init 生成。",
9
+ "primaryLabel": "探索功能"
10
+ },
11
+ "features": {
12
+ "title": "为可视化编辑的网站而设计",
13
+ "description": "起始区块帮助智能体和编辑者理解主题结构。",
14
+ "items": {
15
+ "typedFields": {
16
+ "title": "类型化字段",
17
+ "description": "每个区块都提供清晰的字段结构。"
18
+ },
19
+ "starterPages": {
20
+ "title": "起始页面",
21
+ "description": "模板展示真实可用的区块组合。"
22
+ },
23
+ "cliWorkflow": {
24
+ "title": "CLI 工作流",
25
+ "description": "通过一个工具完成构建、校验、预览和发布。"
26
+ }
27
+ }
28
+ },
29
+ "featuredPosts": {
30
+ "title": "精选文章",
31
+ "description": "使用 CMS 文章持续更新首页,无需手动维护卡片。"
32
+ },
33
+ "callToAction": {
34
+ "title": "创建你的第一个页面",
35
+ "description": "自定义此起始模板,或让智能体生成新的草稿。",
36
+ "buttonLabel": "联系我们"
37
+ }
38
+ },
39
+ "services": {
40
+ "title": "服务",
41
+ "hero": {
42
+ "eyebrow": "服务",
43
+ "title": "展示你提供的服务",
44
+ "description": "使用此页面介绍核心服务、套餐或能力。",
45
+ "primaryLabel": "联系我们"
46
+ },
47
+ "features": {
48
+ "title": "服务亮点",
49
+ "description": "将这些卡片替换为你帮助客户的关键方式。"
50
+ }
51
+ },
52
+ "team": {
53
+ "title": "团队",
54
+ "hero": {
55
+ "eyebrow": "团队",
56
+ "title": "介绍工作背后的成员",
57
+ "description": "分享经验、角色以及项目背后的团队故事。",
58
+ "primaryLabel": "与我们合作"
59
+ },
60
+ "testimonial": {
61
+ "quote": "使用此区块展示创始人寄语、客户评价或团队理念。",
62
+ "author": "团队负责人",
63
+ "role": "创始人"
64
+ }
65
+ },
66
+ "about": {
67
+ "title": "关于我们",
68
+ "hero": {
69
+ "eyebrow": "关于我们",
70
+ "title": "从这里开始讲述你的故事",
71
+ "description": "使用此页面介绍项目、受众与品牌承诺。",
72
+ "primaryLabel": "联系我们"
73
+ },
74
+ "testimonial": {
75
+ "quote": "这个起始主题让可编辑内容保持聚焦且可预测。",
76
+ "role": "主题团队"
77
+ }
78
+ },
79
+ "contact": {
80
+ "title": "联系",
81
+ "hero": {
82
+ "eyebrow": "联系",
83
+ "title": "欢迎交流",
84
+ "description": "告诉访客如何联系你,以及接下来会发生什么。",
85
+ "primaryLabel": "发送邮件"
86
+ },
87
+ "callToAction": {
88
+ "title": "开始沟通",
89
+ "description": "将此文案替换为你偏好的联系方式或表单链接。",
90
+ "buttonLabel": "发送邮件"
91
+ }
92
+ }
93
+ }
94
+ }
@@ -7,6 +7,7 @@ import type { ThemeSourceManifest } from "@sudajs/theme-engine";
7
7
  export const sourceManifest: ThemeSourceManifest = {
8
8
  key: "__SUDA_THEME_KEY__",
9
9
  name: "__SUDA_THEME_KEY__",
10
+ previewLocales: ["en", "zh-CN"],
10
11
  categories: ["other"],
11
12
  minEngineVersion: "4.0.0",
12
13
  designSystem: {
@@ -58,7 +59,6 @@ export const sourceManifest: ThemeSourceManifest = {
58
59
  ],
59
60
  },
60
61
  entry: "index.js",
61
- clientEntry: "runtime.client.js",
62
62
  // Required. SudaCloud currently hosts SSR themes only; CSR/Hybrid postures
63
63
  // are on the roadmap. Authoring `renderMode` is the contract a theme uses
64
64
  // to declare which posture it expects from the host.
@@ -0,0 +1,13 @@
1
+ import { createThemePreviewTranslate } from "@sudajs/theme-engine/runtime";
2
+
3
+ import en from "./locales/en.content.json" with { type: "json" };
4
+
5
+ type DotPath<T, Prefix extends string = ""> = T extends string
6
+ ? Prefix
7
+ : {
8
+ [Key in keyof T & string]: DotPath<T[Key], Prefix extends "" ? Key : `${Prefix}.${Key}`>;
9
+ }[keyof T & string];
10
+
11
+ export type PreviewMessageKeys = DotPath<typeof en>;
12
+
13
+ export const t = createThemePreviewTranslate<PreviewMessageKeys>(en);
@@ -1,11 +1,12 @@
1
1
  import type { ThemeCmsTemplates, ThemeStarterPage } from "@sudajs/theme-engine";
2
2
 
3
+ import { t } from "./preview-i18n.js";
3
4
  import { themeAsset } from "./theme-asset.js";
4
5
 
5
6
  export const starterPages: ThemeStarterPage[] = [
6
7
  {
7
8
  slug: "index",
8
- title: "Home",
9
+ title: t("pages.index.title"),
9
10
  isHome: true,
10
11
  data: {
11
12
  root: { props: {} },
@@ -15,10 +16,10 @@ export const starterPages: ThemeStarterPage[] = [
15
16
  props: {
16
17
  id: "Hero-1",
17
18
  logo: themeAsset("assets/brand/suda-logo.svg"),
18
- eyebrow: "Starter page",
19
- title: "Welcome to __SUDA_THEME_KEY__",
20
- description: "This page was generated by suda theme init.",
21
- primaryLabel: "Explore features",
19
+ eyebrow: t("pages.index.hero.eyebrow"),
20
+ title: t("pages.index.hero.title"),
21
+ description: t("pages.index.hero.description"),
22
+ primaryLabel: t("pages.index.hero.primaryLabel"),
22
23
  primaryHref: "#features",
23
24
  },
24
25
  },
@@ -26,12 +27,21 @@ export const starterPages: ThemeStarterPage[] = [
26
27
  type: "FeatureGrid",
27
28
  props: {
28
29
  id: "FeatureGrid-1",
29
- title: "Designed for editable sites",
30
- description: "Starter sections show agents and editors how this theme is structured.",
30
+ title: t("pages.index.features.title"),
31
+ description: t("pages.index.features.description"),
31
32
  features: [
32
- { title: "Typed fields", description: "Each section exposes a clear field schema." },
33
- { title: "Starter pages", description: "Templates show realistic section composition." },
34
- { title: "CLI workflow", description: "Build, validate, preview, and publish from one tool." },
33
+ {
34
+ title: t("pages.index.features.items.typedFields.title"),
35
+ description: t("pages.index.features.items.typedFields.description"),
36
+ },
37
+ {
38
+ title: t("pages.index.features.items.starterPages.title"),
39
+ description: t("pages.index.features.items.starterPages.description"),
40
+ },
41
+ {
42
+ title: t("pages.index.features.items.cliWorkflow.title"),
43
+ description: t("pages.index.features.items.cliWorkflow.description"),
44
+ },
35
45
  ],
36
46
  },
37
47
  },
@@ -39,8 +49,8 @@ export const starterPages: ThemeStarterPage[] = [
39
49
  type: "FeaturedPosts",
40
50
  props: {
41
51
  id: "FeaturedPosts-1",
42
- title: "Featured posts",
43
- description: "Use CMS posts to keep the homepage fresh without manual card editing.",
52
+ title: t("pages.index.featuredPosts.title"),
53
+ description: t("pages.index.featuredPosts.description"),
44
54
  postList: { strategy: "featured", limit: 3 },
45
55
  },
46
56
  },
@@ -48,9 +58,9 @@ export const starterPages: ThemeStarterPage[] = [
48
58
  type: "CallToAction",
49
59
  props: {
50
60
  id: "CallToAction-1",
51
- title: "Launch your first page",
52
- description: "Customize this starter template or let an agent generate a new draft.",
53
- buttonLabel: "Get in touch",
61
+ title: t("pages.index.callToAction.title"),
62
+ description: t("pages.index.callToAction.description"),
63
+ buttonLabel: t("pages.index.callToAction.buttonLabel"),
54
64
  buttonHref: "/contact-us",
55
65
  },
56
66
  },
@@ -59,7 +69,7 @@ export const starterPages: ThemeStarterPage[] = [
59
69
  },
60
70
  {
61
71
  slug: "services",
62
- title: "Services",
72
+ title: t("pages.services.title"),
63
73
  data: {
64
74
  root: { props: {} },
65
75
  content: [
@@ -67,10 +77,10 @@ export const starterPages: ThemeStarterPage[] = [
67
77
  type: "Hero",
68
78
  props: {
69
79
  id: "Hero-Services",
70
- eyebrow: "Services",
71
- title: "Show what you offer",
72
- description: "Use this page to explain core services, packages, or capabilities.",
73
- primaryLabel: "Contact us",
80
+ eyebrow: t("pages.services.hero.eyebrow"),
81
+ title: t("pages.services.hero.title"),
82
+ description: t("pages.services.hero.description"),
83
+ primaryLabel: t("pages.services.hero.primaryLabel"),
74
84
  primaryHref: "/contact-us",
75
85
  },
76
86
  },
@@ -78,8 +88,8 @@ export const starterPages: ThemeStarterPage[] = [
78
88
  type: "FeatureGrid",
79
89
  props: {
80
90
  id: "FeatureGrid-Services",
81
- title: "Service highlights",
82
- description: "Replace these cards with the most important ways you help customers.",
91
+ title: t("pages.services.features.title"),
92
+ description: t("pages.services.features.description"),
83
93
  },
84
94
  },
85
95
  ],
@@ -87,7 +97,7 @@ export const starterPages: ThemeStarterPage[] = [
87
97
  },
88
98
  {
89
99
  slug: "team",
90
- title: "Team",
100
+ title: t("pages.team.title"),
91
101
  data: {
92
102
  root: { props: {} },
93
103
  content: [
@@ -95,10 +105,10 @@ export const starterPages: ThemeStarterPage[] = [
95
105
  type: "Hero",
96
106
  props: {
97
107
  id: "Hero-Team",
98
- eyebrow: "Team",
99
- title: "Introduce the people behind the work",
100
- description: "Share experience, roles, and the human story behind the project.",
101
- primaryLabel: "Work with us",
108
+ eyebrow: t("pages.team.hero.eyebrow"),
109
+ title: t("pages.team.hero.title"),
110
+ description: t("pages.team.hero.description"),
111
+ primaryLabel: t("pages.team.hero.primaryLabel"),
102
112
  primaryHref: "/contact-us",
103
113
  },
104
114
  },
@@ -106,9 +116,9 @@ export const starterPages: ThemeStarterPage[] = [
106
116
  type: "Testimonial",
107
117
  props: {
108
118
  id: "Testimonial-Team",
109
- quote: "Use this block for a founder note, client quote, or team philosophy.",
110
- author: "Team lead",
111
- role: "Founder",
119
+ quote: t("pages.team.testimonial.quote"),
120
+ author: t("pages.team.testimonial.author"),
121
+ role: t("pages.team.testimonial.role"),
112
122
  },
113
123
  },
114
124
  ],
@@ -116,7 +126,7 @@ export const starterPages: ThemeStarterPage[] = [
116
126
  },
117
127
  {
118
128
  slug: "about-us",
119
- title: "About",
129
+ title: t("pages.about.title"),
120
130
  data: {
121
131
  root: { props: {} },
122
132
  content: [
@@ -124,10 +134,10 @@ export const starterPages: ThemeStarterPage[] = [
124
134
  type: "Hero",
125
135
  props: {
126
136
  id: "Hero-About",
127
- eyebrow: "About",
128
- title: "A clean starting point for your story",
129
- description: "Use this page to introduce the project, audience, and promise.",
130
- primaryLabel: "Contact us",
137
+ eyebrow: t("pages.about.hero.eyebrow"),
138
+ title: t("pages.about.hero.title"),
139
+ description: t("pages.about.hero.description"),
140
+ primaryLabel: t("pages.about.hero.primaryLabel"),
131
141
  primaryHref: "/contact-us",
132
142
  },
133
143
  },
@@ -135,9 +145,9 @@ export const starterPages: ThemeStarterPage[] = [
135
145
  type: "Testimonial",
136
146
  props: {
137
147
  id: "Testimonial-About",
138
- quote: "This starter theme keeps the editable surface focused and predictable.",
148
+ quote: t("pages.about.testimonial.quote"),
139
149
  author: "SudaCloud",
140
- role: "Theme team",
150
+ role: t("pages.about.testimonial.role"),
141
151
  },
142
152
  },
143
153
  ],
@@ -145,7 +155,7 @@ export const starterPages: ThemeStarterPage[] = [
145
155
  },
146
156
  {
147
157
  slug: "contact-us",
148
- title: "Contact",
158
+ title: t("pages.contact.title"),
149
159
  data: {
150
160
  root: { props: {} },
151
161
  content: [
@@ -153,10 +163,10 @@ export const starterPages: ThemeStarterPage[] = [
153
163
  type: "Hero",
154
164
  props: {
155
165
  id: "Hero-Contact",
156
- eyebrow: "Contact",
157
- title: "Let's talk",
158
- description: "Tell visitors how to reach you and what happens next.",
159
- primaryLabel: "Email us",
166
+ eyebrow: t("pages.contact.hero.eyebrow"),
167
+ title: t("pages.contact.hero.title"),
168
+ description: t("pages.contact.hero.description"),
169
+ primaryLabel: t("pages.contact.hero.primaryLabel"),
160
170
  primaryHref: "mailto:hello@example.com",
161
171
  },
162
172
  },
@@ -164,9 +174,9 @@ export const starterPages: ThemeStarterPage[] = [
164
174
  type: "CallToAction",
165
175
  props: {
166
176
  id: "CallToAction-Contact",
167
- title: "Start the conversation",
168
- description: "Replace this copy with your preferred contact details or form link.",
169
- buttonLabel: "Send an email",
177
+ title: t("pages.contact.callToAction.title"),
178
+ description: t("pages.contact.callToAction.description"),
179
+ buttonLabel: t("pages.contact.callToAction.buttonLabel"),
170
180
  buttonHref: "mailto:hello@example.com",
171
181
  },
172
182
  },
@@ -1,5 +0,0 @@
1
- "use client";
2
-
3
- import theme from "./index.js";
4
-
5
- export default theme;