design-md-generator 1.0.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/README.md +186 -0
- package/package.json +35 -0
- package/src/cli.js +127 -0
- package/src/extractor.js +721 -0
- package/src/generator.js +904 -0
- package/src/index.js +195 -0
- package/src/local-extractor.js +985 -0
package/README.md
ADDED
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
# Design MD Generator
|
|
2
|
+
|
|
3
|
+
从任意网站或本地源码生成 `DESIGN.md` 文件。使用 Puppeteer 访问在线网站,或通过静态分析本地源码,提取设计令牌(颜色、排版、组件、布局、阴影、响应式断点),输出遵循 [Google Stitch DESIGN.md 格式](https://stitch.withgoogle.com/docs/design-md/overview/) 的结构化 Markdown 文档。
|
|
4
|
+
|
|
5
|
+
## 快速开始
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
# 安装依赖
|
|
9
|
+
npm install
|
|
10
|
+
|
|
11
|
+
# 模式一:从在线网站生成 DESIGN.md
|
|
12
|
+
node src/cli.js url https://stripe.com
|
|
13
|
+
|
|
14
|
+
# 模式二:从本地源码生成 DESIGN.md
|
|
15
|
+
node src/cli.js local ./my-project
|
|
16
|
+
|
|
17
|
+
# 带选项
|
|
18
|
+
node src/cli.js url https://stripe.com -o ./output/stripe-DESIGN.md -n "Stripe" --screenshot --json
|
|
19
|
+
node src/cli.js local ./my-project -o ./output/project-DESIGN.md -p "src/pages/home" "src/components" --json
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## 两种提取模式
|
|
23
|
+
|
|
24
|
+
### 模式一:在线网站提取(URL 模式)
|
|
25
|
+
|
|
26
|
+
通过 Puppeteer 启动无头浏览器访问目标网站,从实时 DOM 和计算样式中提取设计令牌。
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
Usage: design-md url [options] <url>
|
|
30
|
+
|
|
31
|
+
参数:
|
|
32
|
+
url 要提取设计令牌的网站 URL
|
|
33
|
+
|
|
34
|
+
选项:
|
|
35
|
+
-o, --output <path> 输出文件路径(默认:"./DESIGN.md")
|
|
36
|
+
-n, --name <name> 网站名称(不指定则自动检测)
|
|
37
|
+
-t, --timeout <ms> 导航超时时间,毫秒(默认:"30000")
|
|
38
|
+
-s, --screenshot 保存首页截图
|
|
39
|
+
--json 同时输出原始令牌的 JSON 文件
|
|
40
|
+
-w, --wait <selector> 提取前等待指定的 CSS 选择器
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
**使用示例:**
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
# 基本用法(url 是默认子命令)
|
|
47
|
+
design-md url https://stripe.com
|
|
48
|
+
|
|
49
|
+
# 完整选项
|
|
50
|
+
design-md url https://linear.app -o ./output/linear-DESIGN.md -n "Linear" --json --screenshot
|
|
51
|
+
|
|
52
|
+
# SPA 应用需要额外等待
|
|
53
|
+
design-md url https://app.example.com -w ".main-content" -t 60000
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### 模式二:本地源码提取(Local 模式)
|
|
57
|
+
|
|
58
|
+
通过静态分析本地前端源码文件(CSS/SCSS/Less/HTML/JSX/TSX/Vue/Svelte),提取设计令牌。无需浏览器,适用于:
|
|
59
|
+
|
|
60
|
+
- 本地开发中的项目
|
|
61
|
+
- 需要认证才能访问的页面
|
|
62
|
+
- 需要针对特定页面/组件生成设计文档
|
|
63
|
+
- CI/CD 流水线中自动生成设计文档
|
|
64
|
+
|
|
65
|
+
```
|
|
66
|
+
Usage: design-md local [options] <dir>
|
|
67
|
+
|
|
68
|
+
参数:
|
|
69
|
+
dir 要分析的源码目录
|
|
70
|
+
|
|
71
|
+
选项:
|
|
72
|
+
-o, --output <path> 输出文件路径(默认:"./DESIGN.md")
|
|
73
|
+
-n, --name <name> 项目名称(不指定则从 package.json 自动检测)
|
|
74
|
+
-p, --pages <patterns...> 聚焦分析的页面/目录模式(如 "src/pages/home" "components/Header")
|
|
75
|
+
-i, --include <patterns...> 仅包含匹配这些模式的文件
|
|
76
|
+
-e, --exclude <patterns...> 排除匹配这些模式的文件
|
|
77
|
+
--json 同时输出原始令牌的 JSON 文件
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
**使用示例:**
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
# 分析整个项目
|
|
84
|
+
design-md local ./my-react-app
|
|
85
|
+
|
|
86
|
+
# 聚焦特定页面
|
|
87
|
+
design-md local ./my-project -p "src/pages/dashboard" "src/components/shared"
|
|
88
|
+
|
|
89
|
+
# 排除测试文件
|
|
90
|
+
design-md local ./my-project -e "__tests__" "*.test" "*.spec"
|
|
91
|
+
|
|
92
|
+
# 仅分析特定目录下的样式
|
|
93
|
+
design-md local ./my-project -i "src/styles" "src/theme"
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
**支持的文件类型:**
|
|
97
|
+
|
|
98
|
+
| 类别 | 扩展名 |
|
|
99
|
+
|------|--------|
|
|
100
|
+
| 样式文件 | `.css`, `.scss`, `.sass`, `.less`, `.styl`, `.stylus` |
|
|
101
|
+
| 模板文件 | `.html`, `.htm`, `.jsx`, `.tsx`, `.vue`, `.svelte` |
|
|
102
|
+
| 配置文件 | `tailwind.config.*`, `theme.*`, `tokens.*`, `design-tokens.*` |
|
|
103
|
+
|
|
104
|
+
**自动忽略的目录:** `node_modules`, `.git`, `.next`, `.nuxt`, `dist`, `build`, `out`, `.cache`, `coverage`, `vendor`
|
|
105
|
+
|
|
106
|
+
## 提取内容
|
|
107
|
+
|
|
108
|
+
| 类别 | URL 模式提取内容 | Local 模式提取内容 |
|
|
109
|
+
|------|-----------------|-------------------|
|
|
110
|
+
| **CSS 变量** | 从可访问的样式表中提取所有 `--custom-property` | 从所有样式文件中提取 |
|
|
111
|
+
| **颜色** | 从 500+ DOM 元素采样背景色、文字色、边框色 | 从源码中提取 hex/rgb/hsl 颜色值 |
|
|
112
|
+
| **语义颜色** | 从关键元素(body、nav、标题、链接、按钮)提取 | 从 CSS 变量命名推断语义角色 |
|
|
113
|
+
| **排版** | 字体族、完整层级表(字号、字重、行高、字间距) | 从 font-family/font-size 等声明提取 |
|
|
114
|
+
| **组件** | 按钮、卡片、徽章、输入框、导航栏的完整属性 | 从选择器模式匹配提取组件样式块 |
|
|
115
|
+
| **布局** | 间距比例尺、max-width、Grid/Flex 使用情况 | 从 padding/margin/gap 声明提取 |
|
|
116
|
+
| **阴影** | 所有不同的 box-shadow 值及使用频率 | 从 box-shadow 声明提取 |
|
|
117
|
+
| **断点** | 从可访问的样式表中提取媒体查询断点 | 从 @media 规则提取 |
|
|
118
|
+
| **元信息** | 页面标题、描述、主题色、深色/浅色模式检测 | 从 package.json 读取项目信息 |
|
|
119
|
+
| **Tailwind** | — | 自动检测并解析 tailwind.config.* 配置 |
|
|
120
|
+
|
|
121
|
+
## 输出格式(9 大章节)
|
|
122
|
+
|
|
123
|
+
生成的 `DESIGN.md` 遵循标准的 9 章节格式:
|
|
124
|
+
|
|
125
|
+
1. **视觉主题与氛围** — 风格基调、设计哲学、关键特征
|
|
126
|
+
2. **色彩体系与角色** — 语义化颜色及十六进制值、功能角色
|
|
127
|
+
3. **排版规则** — 字体族、完整层级表
|
|
128
|
+
4. **组件样式** — 按钮、卡片、输入框、导航栏及其状态
|
|
129
|
+
5. **布局原则** — 间距比例尺、Grid、留白哲学
|
|
130
|
+
6. **层深与阴影** — 阴影系统、表面层次结构
|
|
131
|
+
7. **设计规范与禁忌** — 设计护栏和反模式
|
|
132
|
+
8. **响应式行为** — 断点、触控目标尺寸、折叠策略
|
|
133
|
+
9. **AI 代理提示指南** — 快速颜色参考、即用型组件提示
|
|
134
|
+
|
|
135
|
+
## 编程接口
|
|
136
|
+
|
|
137
|
+
```javascript
|
|
138
|
+
const { generateDesignMdFromUrl, generateDesignMdFromLocal } = require('design-md-generator');
|
|
139
|
+
|
|
140
|
+
// 模式一:从 URL 提取
|
|
141
|
+
const result1 = await generateDesignMdFromUrl('https://stripe.com', {
|
|
142
|
+
outputPath: './DESIGN.md',
|
|
143
|
+
siteName: 'Stripe',
|
|
144
|
+
timeout: 30000,
|
|
145
|
+
screenshot: true,
|
|
146
|
+
outputJson: true,
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
// 模式二:从本地源码提取
|
|
150
|
+
const result2 = await generateDesignMdFromLocal('./my-project', {
|
|
151
|
+
outputPath: './DESIGN.md',
|
|
152
|
+
siteName: 'My Project',
|
|
153
|
+
pages: ['src/pages/home', 'src/components'],
|
|
154
|
+
exclude: ['__tests__'],
|
|
155
|
+
outputJson: true,
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
// 两种模式返回相同结构
|
|
159
|
+
console.log(result1.markdown); // DESIGN.md 内容
|
|
160
|
+
console.log(result1.tokens); // 原始设计令牌
|
|
161
|
+
console.log(result1.colorCount); // 颜色数量
|
|
162
|
+
console.log(result1.fontCount); // 字体族数量
|
|
163
|
+
console.log(result1.componentCount); // 组件样式数量
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
## 已知限制
|
|
167
|
+
|
|
168
|
+
### URL 模式
|
|
169
|
+
|
|
170
|
+
- **跨域样式表**:外部 CDN 的 CSS 可能无法提取 CSS 变量
|
|
171
|
+
- **JavaScript 渲染内容**:工具等待 `networkidle2`,但重度 SPA 站点可能需要 `--wait` 选项
|
|
172
|
+
- **Hover/Focus 状态**:仅捕获默认状态(不模拟交互)
|
|
173
|
+
- **深色模式**:提取默认主题;需使用浏览器偏好设置测试深色模式
|
|
174
|
+
- **私有/认证页面**:仅支持公开页面
|
|
175
|
+
|
|
176
|
+
### Local 模式
|
|
177
|
+
|
|
178
|
+
- **CSS-in-JS**:不支持 styled-components、emotion 等运行时 CSS 方案的完整解析
|
|
179
|
+
- **动态样式**:无法捕获通过 JavaScript 动态生成的样式
|
|
180
|
+
- **计算值**:无法获取浏览器计算后的实际值(如 `calc()` 表达式)
|
|
181
|
+
- **Tailwind 工具类**:仅从配置文件提取,不解析模板中的工具类使用
|
|
182
|
+
- **主题切换**:仅分析源码中的静态声明
|
|
183
|
+
|
|
184
|
+
## 许可证
|
|
185
|
+
|
|
186
|
+
MIT
|
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "design-md-generator",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Generate DESIGN.md files from any website using Puppeteer to extract design tokens",
|
|
5
|
+
"main": "src/index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"design-md-generator": "./src/cli.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"src/**/*.js",
|
|
11
|
+
"README.md"
|
|
12
|
+
],
|
|
13
|
+
"engines": {
|
|
14
|
+
"node": ">=18.0.0"
|
|
15
|
+
},
|
|
16
|
+
"scripts": {
|
|
17
|
+
"generate": "node src/cli.js",
|
|
18
|
+
"test": "echo \"Error: no test specified\" && exit 1",
|
|
19
|
+
"postinstall": "echo '✅ design-md-generator installed! Run: design-md-generator <url> to generate a DESIGN.md'"
|
|
20
|
+
},
|
|
21
|
+
"keywords": [
|
|
22
|
+
"design-system",
|
|
23
|
+
"design-md",
|
|
24
|
+
"design-tokens",
|
|
25
|
+
"puppeteer",
|
|
26
|
+
"css-extraction",
|
|
27
|
+
"style-guide",
|
|
28
|
+
"web-scraping"
|
|
29
|
+
],
|
|
30
|
+
"license": "MIT",
|
|
31
|
+
"dependencies": {
|
|
32
|
+
"puppeteer": "^24.0.0",
|
|
33
|
+
"commander": "^13.0.0"
|
|
34
|
+
}
|
|
35
|
+
}
|
package/src/cli.js
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* CLI entry point for design-md-generator
|
|
5
|
+
* Usage:
|
|
6
|
+
* design-md-generator <url> [options] — Extract from live website via Puppeteer
|
|
7
|
+
* design-md-generator local <dir> [options] — Extract from local source code
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const { program } = require('commander');
|
|
11
|
+
const path = require('path');
|
|
12
|
+
const { generateDesignMdFromUrl, generateDesignMdFromLocal } = require('./index');
|
|
13
|
+
|
|
14
|
+
// ============================================================
|
|
15
|
+
// Default command: extract from URL via Puppeteer
|
|
16
|
+
// ============================================================
|
|
17
|
+
program
|
|
18
|
+
.name('design-md-generator')
|
|
19
|
+
.description('Generate DESIGN.md from any website or local source code')
|
|
20
|
+
.version('1.0.0');
|
|
21
|
+
|
|
22
|
+
program
|
|
23
|
+
.command('url', { isDefault: true })
|
|
24
|
+
.description('Generate DESIGN.md from a live website URL using Puppeteer')
|
|
25
|
+
.argument('<url>', 'Website URL to extract design tokens from')
|
|
26
|
+
.option('-o, --output <path>', 'Output file path', './DESIGN.md')
|
|
27
|
+
.option('-n, --name <name>', 'Site name (auto-detected if not provided)')
|
|
28
|
+
.option('-t, --timeout <ms>', 'Navigation timeout in milliseconds', '30000')
|
|
29
|
+
.option('-s, --screenshot', 'Save a screenshot of the homepage')
|
|
30
|
+
.option('--json', 'Also output raw tokens as JSON')
|
|
31
|
+
.option('-w, --wait <selector>', 'Wait for a specific CSS selector before extracting')
|
|
32
|
+
.action(async (url, options) => {
|
|
33
|
+
try {
|
|
34
|
+
// Normalize URL
|
|
35
|
+
if (!url.startsWith('http://') && !url.startsWith('https://')) {
|
|
36
|
+
url = 'https://' + url;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
console.log(`\n🎨 Design MD Generator (URL mode)\n`);
|
|
40
|
+
console.log(` URL: ${url}`);
|
|
41
|
+
console.log(` Output: ${options.output}`);
|
|
42
|
+
console.log('');
|
|
43
|
+
|
|
44
|
+
const result = await generateDesignMdFromUrl(url, {
|
|
45
|
+
outputPath: path.resolve(options.output),
|
|
46
|
+
siteName: options.name,
|
|
47
|
+
timeout: parseInt(options.timeout),
|
|
48
|
+
screenshot: options.screenshot,
|
|
49
|
+
outputJson: options.json,
|
|
50
|
+
waitForSelector: options.wait,
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
console.log(`\n✨ Done! DESIGN.md generated successfully.`);
|
|
54
|
+
console.log(` 📄 ${result.outputPath}`);
|
|
55
|
+
if (result.jsonPath) {
|
|
56
|
+
console.log(` 📊 ${result.jsonPath}`);
|
|
57
|
+
}
|
|
58
|
+
if (result.screenshotPath) {
|
|
59
|
+
console.log(` 📸 ${result.screenshotPath}`);
|
|
60
|
+
}
|
|
61
|
+
console.log(` 🎨 ${result.colorCount} colors extracted`);
|
|
62
|
+
console.log(` 🔤 ${result.fontCount} font families detected`);
|
|
63
|
+
console.log(` 📐 ${result.componentCount} component styles captured`);
|
|
64
|
+
console.log('');
|
|
65
|
+
} catch (error) {
|
|
66
|
+
console.error(`\n❌ Error: ${error.message}\n`);
|
|
67
|
+
if (error.message.includes('net::ERR')) {
|
|
68
|
+
console.error(' Could not reach the website. Check the URL and your network connection.');
|
|
69
|
+
}
|
|
70
|
+
if (error.message.includes('timeout')) {
|
|
71
|
+
console.error(' The page took too long to load. Try increasing --timeout.');
|
|
72
|
+
}
|
|
73
|
+
process.exit(1);
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
// ============================================================
|
|
78
|
+
// Local command: extract from local source code
|
|
79
|
+
// ============================================================
|
|
80
|
+
program
|
|
81
|
+
.command('local')
|
|
82
|
+
.description('Generate DESIGN.md from local source code by static analysis')
|
|
83
|
+
.argument('<dir>', 'Directory containing source code to analyze')
|
|
84
|
+
.option('-o, --output <path>', 'Output file path', './DESIGN.md')
|
|
85
|
+
.option('-n, --name <name>', 'Project name (auto-detected from package.json if not provided)')
|
|
86
|
+
.option('-p, --pages <patterns...>', 'Page/directory patterns to focus on (e.g. "src/pages/home" "components/Header")')
|
|
87
|
+
.option('-i, --include <patterns...>', 'Include only files matching these patterns')
|
|
88
|
+
.option('-e, --exclude <patterns...>', 'Exclude files matching these patterns')
|
|
89
|
+
.option('--json', 'Also output raw tokens as JSON')
|
|
90
|
+
.action(async (dir, options) => {
|
|
91
|
+
try {
|
|
92
|
+
console.log(`\n🎨 Design MD Generator (Local mode)\n`);
|
|
93
|
+
console.log(` Directory: ${path.resolve(dir)}`);
|
|
94
|
+
console.log(` Output: ${options.output}`);
|
|
95
|
+
if (options.pages) {
|
|
96
|
+
console.log(` Pages: ${options.pages.join(', ')}`);
|
|
97
|
+
}
|
|
98
|
+
console.log('');
|
|
99
|
+
|
|
100
|
+
const result = await generateDesignMdFromLocal(dir, {
|
|
101
|
+
outputPath: path.resolve(options.output),
|
|
102
|
+
siteName: options.name,
|
|
103
|
+
pages: options.pages || [],
|
|
104
|
+
include: options.include || [],
|
|
105
|
+
exclude: options.exclude || [],
|
|
106
|
+
outputJson: options.json,
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
console.log(`\n✨ Done! DESIGN.md generated successfully.`);
|
|
110
|
+
console.log(` 📄 ${result.outputPath}`);
|
|
111
|
+
if (result.jsonPath) {
|
|
112
|
+
console.log(` 📊 ${result.jsonPath}`);
|
|
113
|
+
}
|
|
114
|
+
console.log(` 🎨 ${result.colorCount} colors extracted`);
|
|
115
|
+
console.log(` 🔤 ${result.fontCount} font families detected`);
|
|
116
|
+
console.log(` 📐 ${result.componentCount} component styles captured`);
|
|
117
|
+
console.log('');
|
|
118
|
+
} catch (error) {
|
|
119
|
+
console.error(`\n❌ Error: ${error.message}\n`);
|
|
120
|
+
if (error.message.includes('not found')) {
|
|
121
|
+
console.error(' Directory not found. Check the path and try again.');
|
|
122
|
+
}
|
|
123
|
+
process.exit(1);
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
program.parse();
|