@lark-apaas/coding-templates 0.1.7 → 0.1.8

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/README.md CHANGED
@@ -7,47 +7,36 @@ OpenClaw 项目模板包,供 mclaw CLI 使用。
7
7
  | 模板 | 目录 | 说明 |
8
8
  |---|---|---|
9
9
  | html | `template-html/` | 纯 HTML,零依赖,原型验证 |
10
- | nextjs-static | `template-nextjs-static/` | Next.js Pages Router,静态导出,纯前端 |
11
- | nextjs-fullstack | `template-nextjs-fullstack/` | Next.js App Router + Drizzle + PostgreSQL,全栈 |
10
+ | vite-react | `template-vite-react/` | Vite + React 19 + Tailwind CSS + shadcn/ui |
12
11
 
13
12
  ## 包结构
14
13
 
15
14
  ```
16
- template/
15
+ coding-templates/
17
16
  ├── package.json # npm 包定义(@lark-apaas/coding-templates)
18
17
  ├── meta.json # 模板元数据(业务侧生成,名称/描述/特性标签)
19
18
  ├── template-html/
20
19
  │ ├── _gitignore
21
- │ ├── package.json # mclaw.type: "html"
20
+ │ ├── package.json # mclaw.stack: "html"
22
21
  │ └── index.html
23
- ├── template-nextjs-static/
24
- ├── _gitignore # CLI 复制时重命名为 .gitignore
25
- ├── package.json # mclaw.type: "nextjs-static",name 为 {{projectName}}
26
- ├── tailwind.config.ts
27
- │ ├── eslint.config.js
28
- │ ├── postcss.config.js
29
- │ ├── components.json
30
- │ └── src/
31
- │ ├── pages/ # Pages Router 路由
32
- │ ├── components/ui/ # shadcn/ui 58 个组件
33
- │ ├── hooks/
34
- │ ├── lib/
35
- │ └── styles/globals.css
36
- └── template-nextjs-fullstack/
37
- ├── _gitignore
38
- ├── _env.local.example # CLI 复制时重命名为 .env.local.example
39
- ├── package.json # mclaw.type: "nextjs-fullstack",name 为 {{projectName}}
40
- ├── drizzle.config.ts
41
- ├── tailwind.config.ts
22
+ └── template-vite-react/
23
+ ├── _gitignore # CLI 复制时重命名为 .gitignore
24
+ ├── package.json # mclaw.stack: "vite-react",name 为 {{projectName}}
25
+ ├── vite.config.ts
42
26
  ├── eslint.config.js
43
- ├── postcss.config.js
44
27
  ├── components.json
45
- └── src/
46
- ├── app/ # App Router 路由 + Server Actions
47
- ├── components/ui/ # shadcn/ui 58 个组件
48
- ├── db/ # Drizzle ORM schema + 连接
49
- ├── hooks/
50
- └── lib/
28
+ ├── client/
29
+ ├── index.html
30
+ ├── public/
31
+ │ └── src/
32
+ ├── app.tsx # 路由注册
33
+ │ ├── index.css # 全局样式 + 主题变量
34
+ │ ├── components/ui/ # shadcn/ui 组件(new-york 风格)
35
+ │ ├── pages/
36
+ │ ├── hooks/
37
+ │ └── lib/
38
+ └── shared/ # 前后端共享代码
39
+ └── types.ts
51
40
  ```
52
41
 
53
42
  ## 特殊文件命名
@@ -57,72 +46,35 @@ npm publish 会忽略 `.gitignore` 和 `.env*`,因此模板中使用 `_` 前
57
46
  | 模板中的文件名 | CLI 复制后的文件名 |
58
47
  |---|---|
59
48
  | `_gitignore` | `.gitignore` |
60
- | `_env.local.example` | `.env.local.example` |
49
+ | `_env.local` | `.env.local` |
61
50
 
62
51
  ## 模板类型标识
63
52
 
64
- 每个模板的 `package.json` 包含 `mclaw.type` 字段,标识模板类型:
53
+ 每个模板的 `package.json` 包含 `mclaw.stack` 字段,标识模板类型:
65
54
 
66
55
  ```json
67
56
  {
68
57
  "mclaw": {
69
- "type": "nextjs-static"
58
+ "stack": "vite-react",
59
+ "stackVersion": "0.1.0"
70
60
  }
71
61
  }
72
62
  ```
73
63
 
74
- `meta.json` 由业务侧生成和维护。若 `meta.json` 丢失,CLI 可扫描各模板目录的 `package.json` 中的 `mclaw.type` 恢复。
75
-
76
- ## mclaw CLI 集成方式
77
-
78
- ```javascript
79
- const path = require('path');
80
- const fs = require('fs-extra');
81
-
82
- // 1. 读取模板元数据
83
- const meta = require('@lark-apaas/coding-templates/meta.json');
84
-
85
- // 2. 定位模板目录
86
- const templateDir = path.dirname(require.resolve('@lark-apaas/coding-templates/meta.json'));
87
- const srcDir = path.join(templateDir, selectedTemplate.dir);
88
-
89
- // 3. 复制到目标目录
90
- await fs.copy(srcDir, targetDir);
91
-
92
- // 4. 重命名特殊文件
93
- const renames = { '_gitignore': '.gitignore', '_env.local.example': '.env.local.example' };
94
- for (const [from, to] of Object.entries(renames)) {
95
- const filePath = path.join(targetDir, from);
96
- if (fs.existsSync(filePath)) {
97
- await fs.rename(filePath, path.join(targetDir, to));
98
- }
99
- }
100
-
101
- // 5. 替换 package.json 中的占位符
102
- const pkgPath = path.join(targetDir, 'package.json');
103
- if (fs.existsSync(pkgPath)) {
104
- let content = await fs.readFile(pkgPath, 'utf-8');
105
- content = content.replace('{{projectName}}', projectName);
106
- await fs.writeFile(pkgPath, content);
107
- }
108
- ```
109
-
110
64
  ## 技术栈
111
65
 
112
- 两个 Next.js 模板共享相同的技术选型:
113
-
114
- - **Next.js 16** + React 19 + TypeScript 5
115
- - **Tailwind CSS 4**(`@tailwindcss/postcss`,CSS-first 配置)
116
- - **shadcn/ui**(base-nova 风格,lucide 图标,58 个组件预装)
117
- - **ESLint 9** flat config(`@eslint/js` + `typescript-eslint`,无 `eslint-config-next`)
118
- - **next-themes**(暗色模式)
66
+ vite-react 模板技术选型:
119
67
 
120
- fullstack 模板额外包含:Drizzle ORM + PostgreSQL + Zod
68
+ - **Vite 8** + React 19 + TypeScript 5
69
+ - **Tailwind CSS 4**(`@tailwindcss/vite`,CSS-first 配置)
70
+ - **shadcn/ui**(new-york 风格,lucide 图标,radix-ui 基础组件)
71
+ - **ESLint 9** flat config
72
+ - **react-router-dom** 客户端路由
121
73
 
122
74
  ## 发版
123
75
 
124
76
  ```bash
125
- cd template
77
+ cd packages/openclaw/coding-templates
126
78
  npm version patch
127
79
  npm publish
128
80
  ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lark-apaas/coding-templates",
3
- "version": "0.1.7",
3
+ "version": "0.1.8",
4
4
  "description": "OpenClaw project templates for mclaw CLI",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -6,7 +6,7 @@
6
6
  - 样式: Tailwind CSS v4
7
7
  - UI 组件: shadcn/ui `import { Button } from "@/components/ui/button";`
8
8
  - 图标: lucide-react `import { SearchIcon } from "lucide-react";`
9
- - 图表: ReactECharts `import ReactECharts from "echarts-for-react";`
9
+ - 图表: recharts `import { LineChart } from "recharts";`
10
10
  - 动画: framer-motion `import { motion } from "framer-motion";`
11
11
  - 路由: react-router-dom `import { Link, useNavigate } from "react-router-dom";`
12
12
 
@@ -61,8 +61,7 @@
61
61
  "react-router-dom": "^7.13.2",
62
62
  "recharts": "^3.8.0",
63
63
  "sonner": "^2.0.7",
64
- "streamdown": "^1.6.10",
65
- "tailwind-merge": "^3.5.0",
64
+ "tailwind-merge": "^3.5.0",
66
65
  "tailwindcss": "^4.2.2",
67
66
  "tw-animate-css": "^1.2.0",
68
67
  "vaul": "^1.1.2",
@@ -1,134 +0,0 @@
1
- # shadcn/ui 组件开发指南
2
-
3
- ## 核心原则
4
-
5
- - 组件位置:`/client/src/components/ui/`
6
- - 图标库:必须使用 `lucide-react`,禁用 Emoji
7
- - 查阅源码实现:直接读取组件源码了解最新组件实现
8
- - 交互系统:Button、Badge 使用 elevate 遮罩系统处理 hover/active 状态(通过 `::after` 伪元素叠加 `--elevate-1` / `--elevate-2`)
9
-
10
- ## 关键组件规范
11
-
12
- #### 1. Button 组件
13
-
14
- **导入路径**:`import { Button } from '@/components/ui/button'`
15
-
16
- **Props**:
17
- - `variant`: `"link" | "default" | "destructive" | "outline" | "secondary" | "ghost"` - 按钮样式变体
18
-
19
- ```tsx
20
- // ✅ 使用变体
21
- <Button variant="secondary">标准按钮</Button>
22
-
23
- // ✅ 自定义颜色时必须配对前景色
24
- <Button className="bg-primary text-primary-foreground">自定义按钮</Button>
25
- ```
26
-
27
- #### 2. Badge 组件
28
-
29
- **导入路径**:`import { Badge } from '@/components/ui/badge'`
30
-
31
- **Props**:
32
- - `variant`: `"default" | "destructive" | "outline" | "secondary"` - 样式变体
33
-
34
- ```tsx
35
- // ✅ 使用变体
36
- <Badge>默认</Badge>
37
- <Badge variant="secondary">次要</Badge>
38
- <Badge variant="outline">边框</Badge>
39
- <Badge variant="destructive">危险</Badge>
40
- ```
41
-
42
- #### 3. Alert 组件
43
-
44
- **导入路径**:`import { Alert, AlertTitle, AlertDescription, AlertAction } from '@/components/ui/alert'`
45
-
46
- **Props**:
47
- - `variant`: `"default" | "destructive" | "success" | "warning"` - 样式变体
48
-
49
- ```tsx
50
- // ✅ default / destructive / success / warning 四种变体
51
- <Alert variant="success">
52
- <CheckCircle className="size-4" />
53
- <AlertTitle>操作成功</AlertTitle>
54
- <AlertDescription>您的更改已保存</AlertDescription>
55
- </Alert>
56
-
57
- <Alert variant="warning">
58
- <AlertTriangle className="size-4" />
59
- <AlertTitle>警告</AlertTitle>
60
- <AlertDescription>请注意检查</AlertDescription>
61
- </Alert>
62
-
63
- ```
64
-
65
- #### 4. Empty 组件
66
-
67
- **导入路径**:`import { Empty, EmptyHeader, EmptyMedia, EmptyTitle, EmptyDescription, EmptyContent } from '@/components/ui/empty'`
68
-
69
- **子组件 Props**:
70
- - `EmptyMedia` 组件的 `variant`: `"default" | "icon"` - 媒体展示方式
71
-
72
- ```tsx
73
- // 标准结构:EmptyHeader 包含 EmptyMedia + EmptyTitle + EmptyDescription
74
- <Empty>
75
- <EmptyHeader>
76
- <EmptyMedia variant="icon">
77
- <SearchIcon className="size-6" />
78
- </EmptyMedia>
79
- <EmptyTitle>暂无数据</EmptyTitle>
80
- <EmptyDescription>当前没有找到相关内容</EmptyDescription>
81
- </EmptyHeader>
82
- <EmptyContent>
83
- <Button>添加数据</Button>
84
- </EmptyContent>
85
- </Empty>
86
- ```
87
-
88
- #### 5. Card Padding 系统
89
-
90
- **导入路径**:`import { CardHeader, CardContent, CardFooter } from '@/components/ui/card'`
91
-
92
- - `CardHeader`: `p-6` (24px 全方向)
93
- - `CardContent`: `p-6 pt-0` (与 header 无缝衔接)
94
- - `CardFooter`: `p-6 pt-0` (与 content 无缝衔接)
95
-
96
- #### 6. Dialog 组件
97
-
98
- **导入路径**:`import { Dialog, DialogContent } from '@/components/ui/dialog'`
99
-
100
- Dialog 默认提供了右上角的close能力,同时也提供了自定义关闭按钮的能力,即通过设置showCloseButton为false来关闭默认的关闭按钮。所以当默认存在close时,禁止在内容区域提供自定义的关闭按钮。
101
-
102
- #### 7. Image 组件
103
-
104
- **导入路径**:`import { Image } from '@/components/ui/image'`
105
-
106
- **Props**:支持原生 `<img>` 标签所有属性。
107
-
108
- ```typescript
109
- interface ImageProps extends React.ImgHTMLAttributes<HTMLImageElement>
110
- ```
111
-
112
- **使用规范**:
113
-
114
- 1. **响应式图片场景**:当图片尺寸需要根据视口宽度变化时,必须设置 `sizes` 属性
115
- 2. **固定尺寸图片场景**:当图片有固定尺寸时,必须设置 `width` 属性(number 类型)
116
- 3. **必须提供有意义的 `alt` 属性**
117
-
118
- ```tsx
119
- // ✅ 响应式图片
120
- <Image
121
- src="/path/to/image.jpg"
122
- alt="描述文字"
123
- sizes="(max-width: 768px) 100vw, 50vw"
124
- />
125
-
126
- // ✅ 固定尺寸图片(width/height 使用 number)
127
- <Image
128
- src="/path/to/image.jpg"
129
- alt="描述文字"
130
- width={300}
131
- height={200}
132
- />
133
-
134
- ```
@@ -1,186 +0,0 @@
1
- 'use client';
2
-
3
- import * as React from 'react';
4
- import { Streamdown as StreamdownPrimitive } from 'streamdown';
5
-
6
- import { cn } from '@/lib/utils';
7
- import { omit } from 'es-toolkit';
8
-
9
- const defaultComponents: NonNullable<
10
- React.ComponentProps<typeof StreamdownPrimitive>['components']
11
- > = {
12
- ol: ({ className, ...props }) => (
13
- <ol
14
- className={cn(className)}
15
- data-streamdown="ordered-list"
16
- {...omit(props, ['node'])}
17
- />
18
- ),
19
- li: ({ className, ...props }) => (
20
- <li
21
- className={cn(className)}
22
- data-streamdown="list-item"
23
- {...omit(props, ['node'])}
24
- />
25
- ),
26
- ul: ({ className, ...props }) => (
27
- <ul
28
- className={cn(className)}
29
- data-streamdown="unordered-list"
30
- {...omit(props, ['node'])}
31
- />
32
- ),
33
- hr: ({ className, ...props }) => (
34
- <hr
35
- className={cn(className)}
36
- data-streamdown="horizontal-rule"
37
- {...omit(props, ['node'])}
38
- />
39
- ),
40
- strong: ({ className, ...props }) => (
41
- <strong
42
- className={cn(className)}
43
- data-streamdown="strong"
44
- {...omit(props, ['node'])}
45
- />
46
- ),
47
- a: ({ className, href, ...props }) => {
48
- const isIncomplete = href === 'streamdown:incomplete-link';
49
-
50
- return (
51
- <a
52
- className={cn(className)}
53
- data-incomplete={isIncomplete}
54
- data-streamdown="link"
55
- href={href}
56
- rel={props.rel ?? 'noreferrer'}
57
- target={props.target ?? '_blank'}
58
- {...omit(props, ['node'])}
59
- />
60
- );
61
- },
62
- h1: ({ className, ...props }) => (
63
- <h1
64
- className={cn(className)}
65
- data-streamdown="heading-1"
66
- {...omit(props, ['node'])}
67
- />
68
- ),
69
- h2: ({ className, ...props }) => (
70
- <h2
71
- className={cn(className)}
72
- data-streamdown="heading-2"
73
- {...omit(props, ['node'])}
74
- />
75
- ),
76
- h3: ({ className, ...props }) => (
77
- <h3
78
- className={cn(className)}
79
- data-streamdown="heading-3"
80
- {...omit(props, ['node'])}
81
- />
82
- ),
83
- h4: ({ className, ...props }) => (
84
- <h4
85
- className={cn(className)}
86
- data-streamdown="heading-4"
87
- {...omit(props, ['node'])}
88
- />
89
- ),
90
- h5: ({ className, ...props }) => (
91
- <h5
92
- className={cn(className)}
93
- data-streamdown="heading-5"
94
- {...omit(props, ['node'])}
95
- />
96
- ),
97
- h6: ({ className, ...props }) => (
98
- <h6
99
- className={cn(className)}
100
- data-streamdown="heading-6"
101
- {...omit(props, ['node'])}
102
- />
103
- ),
104
- table: ({ className, ...props }) => (
105
- <table
106
- className={cn(className)}
107
- data-streamdown="table-wrapper"
108
- {...omit(props, ['node'])}
109
- />
110
- ),
111
- thead: ({ className, ...props }) => (
112
- <thead
113
- className={cn(className)}
114
- data-streamdown="table-header"
115
- {...omit(props, ['node'])}
116
- />
117
- ),
118
- tbody: ({ className, ...props }) => (
119
- <tbody
120
- className={cn(className)}
121
- data-streamdown="table-body"
122
- {...omit(props, ['node'])}
123
- />
124
- ),
125
- tr: ({ className, ...props }) => (
126
- <tr
127
- className={cn(className)}
128
- data-streamdown="table-row"
129
- {...omit(props, ['node'])}
130
- />
131
- ),
132
- th: ({ className, ...props }) => (
133
- <th
134
- className={cn(className)}
135
- data-streamdown="table-header-cell"
136
- {...omit(props, ['node'])}
137
- />
138
- ),
139
- td: ({ className, ...props }) => (
140
- <td
141
- className={cn(className)}
142
- data-streamdown="table-cell"
143
- {...omit(props, ['node'])}
144
- />
145
- ),
146
- blockquote: ({ className, ...props }) => (
147
- <blockquote
148
- className={cn(className)}
149
- data-streamdown="blockquote"
150
- {...omit(props, ['node'])}
151
- />
152
- ),
153
- sup: ({ className, ...props }) => (
154
- <sup className={cn(className)} {...omit(props, ['node'])} />
155
- ),
156
- sub: ({ className, ...props }) => (
157
- <sub className={cn(className)} {...omit(props, ['node'])} />
158
- ),
159
- p: ({ className, ...props }) => (
160
- <p className={cn(className)} {...omit(props, ['node'])} />
161
- ),
162
- section: ({ className, ...props }) => (
163
- <section className={cn(className)} {...omit(props, ['node'])} />
164
- ),
165
- img: ({ className, alt, ...props }) => (
166
- <img className={cn(className)} alt={alt ?? ''} {...omit(props, ['node'])} />
167
- ),
168
- pre: ({ children }) => <div className="not-prose">{children}</div>,
169
- };
170
-
171
- export function Streamdown({
172
- className,
173
- components,
174
- ...props
175
- }: React.ComponentProps<typeof StreamdownPrimitive>) {
176
- return (
177
- <StreamdownPrimitive
178
- className={cn(
179
- 'prose max-w-none dark:prose-invert',
180
- className,
181
- )}
182
- components={{ ...defaultComponents, ...components }}
183
- {...props}
184
- />
185
- );
186
- }