@deppon/deppon-prd-mcp 0.1.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 +83 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +16 -0
- package/dist/config.d.ts +8 -0
- package/dist/config.js +30 -0
- package/dist/core/prd-generator.d.ts +4 -0
- package/dist/core/prd-generator.js +133 -0
- package/dist/core/project-store.d.ts +23 -0
- package/dist/core/project-store.js +77 -0
- package/dist/core/prototype-generator.d.ts +2 -0
- package/dist/core/prototype-generator.js +272 -0
- package/dist/core/types.d.ts +204 -0
- package/dist/core/types.js +160 -0
- package/dist/http/server.d.ts +2 -0
- package/dist/http/server.js +115 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/mcp/server.d.ts +4 -0
- package/dist/mcp/server.js +194 -0
- package/package.json +55 -0
- package/templates/examples/README.md +17 -0
- package/templates/examples/app-shell-navigation/layout-spec.md +54 -0
- package/templates/examples/app-shell-navigation/pages/demo-form.html +57 -0
- package/templates/examples/app-shell-navigation/pages/demo-home.html +92 -0
- package/templates/examples/app-shell-navigation/pages/demo-list.html +47 -0
- package/templates/examples/app-shell-navigation/prd.md +164 -0
- package/templates/examples/app-shell-navigation/prototype.html +794 -0
- package/templates/examples/backend-list/prd.md +97 -0
- package/templates/examples/backend-list/prototype.html +378 -0
- package/templates/examples/data-dashboard/prd.md +127 -0
- package/templates/examples/data-dashboard/prototype.html +281 -0
- package/templates/examples/form-edit/prd.md +108 -0
- package/templates/examples/form-edit/prototype.html +280 -0
- package/templates/examples/form-preview/prd.md +106 -0
- package/templates/examples/form-preview/prototype.html +240 -0
- package/templates/examples/list-crud/prd.md +151 -0
- package/templates/examples/list-crud/prototype.html +1348 -0
- package/templates/examples/user-frontend/prd.md +86 -0
- package/templates/examples/user-frontend/prototype.html +223 -0
- package/templates/template/app-shell-navigation-prd-template.md +180 -0
- package/templates/template/backend-form-edit-prd-template.md +116 -0
- package/templates/template/backend-form-preview-prd-template.md +112 -0
- package/templates/template/backend-list-prd-template.md +120 -0
- package/templates/template/data-prd-template.md +194 -0
- package/templates/template/user-frontend-prd-template.md +59 -0
- package/web/app.js +342 -0
- package/web/index.html +106 -0
- package/web/styles.css +100 -0
package/README.md
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# @deppon/deppon-prd-mcp
|
|
2
|
+
|
|
3
|
+
私有化 **PRD 生成 + 交互原型** MCP 服务,基于仓库内 `deppon-prd-generator` skill 模板。
|
|
4
|
+
|
|
5
|
+
## 功能
|
|
6
|
+
|
|
7
|
+
1. **Web 控制台**(`http://127.0.0.1:3847`):填写 PRD 要素(标题、背景、筛选字段、列表列等)→ 生成标准化 `prd.md`
|
|
8
|
+
2. **原型生成**:根据同一套配置生成 Tailwind 高保真 `prototype.html`(含 PRD § 标注弹框)
|
|
9
|
+
3. **在线动态调整**:原型 iframe 支持 `?edit=1` 直接改标题等文案;Studio 侧可改工具栏/行操作并实时预览
|
|
10
|
+
4. **MCP 工具**:供 Cursor Agent 调用 `generate_prd`、`generate_prototype`、`open_prd_studio` 等
|
|
11
|
+
|
|
12
|
+
## 安装(消费方)
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npm install -g @deppon/deppon-prd-mcp
|
|
16
|
+
# 或无需全局安装,直接在 MCP 配置里用 npx
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## 快速开始
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
# 仅 Web 控制台
|
|
23
|
+
npx @deppon/deppon-prd-mcp --web-only
|
|
24
|
+
|
|
25
|
+
# MCP + Web(供 AI 工具 stdio 调用时自动拉起 Web)
|
|
26
|
+
npx @deppon/deppon-prd-mcp
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
浏览器打开 Web 控制台:**http://127.0.0.1:3847**
|
|
30
|
+
|
|
31
|
+
## Cursor / Claude Desktop MCP 配置
|
|
32
|
+
|
|
33
|
+
```json
|
|
34
|
+
{
|
|
35
|
+
"mcpServers": {
|
|
36
|
+
"deppon-prd": {
|
|
37
|
+
"command": "npx",
|
|
38
|
+
"args": ["-y", "@deppon/deppon-prd-mcp@latest"],
|
|
39
|
+
"env": {
|
|
40
|
+
"DEPPON_PRD_WORKSPACE": "/绝对路径/到你的项目根目录",
|
|
41
|
+
"DEPPON_PRD_HTTP_PORT": "3847"
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
> monorepo 内开发时可改用本地路径:`"command": "node", "args": ["packages/deppon-prd-mcp/dist/cli.js"]`
|
|
49
|
+
|
|
50
|
+
## 发布(维护方)
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
cd packages/deppon-prd-mcp
|
|
54
|
+
npm run build
|
|
55
|
+
npm run publish:auto
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## 输出目录
|
|
59
|
+
|
|
60
|
+
```
|
|
61
|
+
src/prototypes/<slug>/
|
|
62
|
+
├── project.config.json # 结构化配置(可 round-trip 编辑)
|
|
63
|
+
├── prd.md # 标准化 PRD(默认 DP-IT 标题)
|
|
64
|
+
└── prototype.html # 可交互原型
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## MCP 工具
|
|
68
|
+
|
|
69
|
+
| 工具 | 说明 |
|
|
70
|
+
|------|------|
|
|
71
|
+
| `generate_prd` | 从 pageName 等要素生成 PRD 并落盘 |
|
|
72
|
+
| `generate_prototype` | 从已有 slug 重新生成 prototype.html |
|
|
73
|
+
| `list_prd_projects` | 列出已生成项目 |
|
|
74
|
+
| `get_prd_project` | 读取项目配置与 PRD 摘要 |
|
|
75
|
+
| `open_prd_studio` | 返回 Web 控制台 URL |
|
|
76
|
+
|
|
77
|
+
## 环境变量
|
|
78
|
+
|
|
79
|
+
| 变量 | 默认 | 说明 |
|
|
80
|
+
|------|------|------|
|
|
81
|
+
| `DEPPON_PRD_WORKSPACE` | `process.cwd()` | 仓库根目录 |
|
|
82
|
+
| `DEPPON_PRD_HTTP_PORT` | `3847` | Web 控制台端口 |
|
|
83
|
+
| `DEPPON_PRD_HTTP_HOST` | `127.0.0.1` | 绑定地址 |
|
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { startMcpServer } from './mcp/server.js';
|
|
3
|
+
import { startWebOnly } from './http/server.js';
|
|
4
|
+
const webOnly = process.argv.includes('--web-only');
|
|
5
|
+
if (webOnly) {
|
|
6
|
+
startWebOnly().catch(err => {
|
|
7
|
+
console.error('[deppon-prd-mcp] fatal:', err);
|
|
8
|
+
process.exit(1);
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
else {
|
|
12
|
+
startMcpServer().catch(err => {
|
|
13
|
+
console.error('[deppon-prd-mcp] fatal:', err);
|
|
14
|
+
process.exit(1);
|
|
15
|
+
});
|
|
16
|
+
}
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export declare function getPackageRoot(): string;
|
|
2
|
+
export declare function getTemplatesRoot(): string;
|
|
3
|
+
export declare function getWebRoot(): string;
|
|
4
|
+
export declare function getWorkspaceRoot(): string;
|
|
5
|
+
export declare function getPrototypesRoot(): string;
|
|
6
|
+
export declare function getHttpPort(): number;
|
|
7
|
+
export declare function getHttpHost(): string;
|
|
8
|
+
export declare function getHttpBaseUrl(): string;
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { fileURLToPath } from 'node:url';
|
|
3
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
4
|
+
const PKG_ROOT = path.resolve(__dirname, '..');
|
|
5
|
+
export function getPackageRoot() {
|
|
6
|
+
return PKG_ROOT;
|
|
7
|
+
}
|
|
8
|
+
export function getTemplatesRoot() {
|
|
9
|
+
return path.join(PKG_ROOT, 'templates');
|
|
10
|
+
}
|
|
11
|
+
export function getWebRoot() {
|
|
12
|
+
return path.join(PKG_ROOT, 'web');
|
|
13
|
+
}
|
|
14
|
+
export function getWorkspaceRoot() {
|
|
15
|
+
return process.env.DEPPON_PRD_WORKSPACE || process.cwd();
|
|
16
|
+
}
|
|
17
|
+
export function getPrototypesRoot() {
|
|
18
|
+
return path.join(getWorkspaceRoot(), 'src/prototypes');
|
|
19
|
+
}
|
|
20
|
+
export function getHttpPort() {
|
|
21
|
+
const raw = process.env.DEPPON_PRD_HTTP_PORT || '3847';
|
|
22
|
+
const port = Number.parseInt(raw, 10);
|
|
23
|
+
return Number.isFinite(port) ? port : 3847;
|
|
24
|
+
}
|
|
25
|
+
export function getHttpHost() {
|
|
26
|
+
return process.env.DEPPON_PRD_HTTP_HOST || '127.0.0.1';
|
|
27
|
+
}
|
|
28
|
+
export function getHttpBaseUrl() {
|
|
29
|
+
return `http://${getHttpHost()}:${getHttpPort()}`;
|
|
30
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { type PrdProject } from './types.js';
|
|
2
|
+
export declare function generatePrdMarkdown(project: PrdProject): string;
|
|
3
|
+
export declare function getTemplatePath(project: PrdProject): string | null;
|
|
4
|
+
export declare function getExamplePrototypePath(project: PrdProject): string | null;
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { getTemplatesRoot } from '../config.js';
|
|
4
|
+
import { buildDpItTitle, EXAMPLE_MAP, TEMPLATE_MAP } from './types.js';
|
|
5
|
+
export function generatePrdMarkdown(project) {
|
|
6
|
+
const title = project.useDpItTitle ? buildDpItTitle(project) : `${project.pageName} PRD(产品需求文档)`;
|
|
7
|
+
const moduleName = project.pageName;
|
|
8
|
+
const listTitle = `${moduleName.replace(/列表|管理/g, '').trim() || moduleName}列表`;
|
|
9
|
+
const featureRows = project.features
|
|
10
|
+
.map(f => `| ${f.module} | ${f.feature} | ${f.description} |`)
|
|
11
|
+
.join('\n');
|
|
12
|
+
const permissionRows = project.permissions
|
|
13
|
+
.map(p => `| ${p.point} | ${p.scope} | ${p.dataScope} |`)
|
|
14
|
+
.join('\n');
|
|
15
|
+
const filterRows = project.filters
|
|
16
|
+
.map(f => `| ${f.name} | ${f.type} | ${f.limit} | ${f.matchRule} |`)
|
|
17
|
+
.join('\n');
|
|
18
|
+
const columnRows = project.columns
|
|
19
|
+
.map(c => `| ${c.name} | ${c.description || '—'} | ${c.sortable ? '是' : '否'} |`)
|
|
20
|
+
.join('\n');
|
|
21
|
+
const toolbarText = project.toolbarButtons.map(b => `「${b}」`).join('、');
|
|
22
|
+
const rowActionText = project.rowActions.map(a => `**${a}**`).join('、');
|
|
23
|
+
const crudSections = project.enableCrud
|
|
24
|
+
? `
|
|
25
|
+
### 3.2.1 列表工具栏(表头操作区)
|
|
26
|
+
|
|
27
|
+
- **位置**:列表卡片顶部与表格之间;左侧为「${listTitle}」;右侧为 ${toolbarText}。
|
|
28
|
+
- **禁止**:与页面 H1 并排;默认不与筛选区「查询 / 重置」混排。
|
|
29
|
+
|
|
30
|
+
### 4. ${moduleName}详情(只读)
|
|
31
|
+
|
|
32
|
+
- 行内「查看」打开只读弹层,展示主字段。
|
|
33
|
+
- 关闭后回到列表,不刷新分页位置(除非业务要求)。
|
|
34
|
+
|
|
35
|
+
### 5. 新增 / 编辑表单
|
|
36
|
+
|
|
37
|
+
- **入口**:表头「${project.createButtonLabel || `新建${moduleName}`}」或行内「编辑」。
|
|
38
|
+
- **校验**:必填项、格式与长度按字段定义;提交成功后 Toast 提示并刷新列表(原型 mock)。
|
|
39
|
+
|
|
40
|
+
### 6. 删除
|
|
41
|
+
|
|
42
|
+
- 行内「删除」二次确认;确认后移除(原型 mock)。
|
|
43
|
+
`
|
|
44
|
+
: `
|
|
45
|
+
> **本期说明**:无新建 / 编辑 / 删除弹层,仅筛选 + 分页列表。
|
|
46
|
+
`;
|
|
47
|
+
return `# ${title}
|
|
48
|
+
|
|
49
|
+
> **交互原型**:[打开 prototype.html](./prototype.html)(须 **HTTP** 打开 \`src/prototypes/${project.slug}/\` 目录。)
|
|
50
|
+
|
|
51
|
+
> **生成说明**:由 @deppon/deppon-prd-mcp 基于 deppon-prd-generator skill 模板生成。
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## 1. 引言
|
|
56
|
+
|
|
57
|
+
### 1.1 背景
|
|
58
|
+
|
|
59
|
+
${project.background}
|
|
60
|
+
|
|
61
|
+
### 1.2 功能清单
|
|
62
|
+
|
|
63
|
+
| 模块 | 功能点 | 功能描述 |
|
|
64
|
+
| ---- | ------ | -------- |
|
|
65
|
+
${featureRows}
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## 2. 页面菜单与权限规划
|
|
70
|
+
|
|
71
|
+
### 2.1 页面菜单
|
|
72
|
+
|
|
73
|
+
**菜单路径**:${project.menuPath}
|
|
74
|
+
|
|
75
|
+
### 2.2 权限规划
|
|
76
|
+
|
|
77
|
+
| 权限点 | 操作范围 | 数据范围 |
|
|
78
|
+
| ------ | -------- | -------- |
|
|
79
|
+
${permissionRows}
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
## 3. ${moduleName}需求说明
|
|
84
|
+
|
|
85
|
+
### 3.1 筛选查询区
|
|
86
|
+
|
|
87
|
+
变更筛选后点击「查询」刷新列表并回到第 1 页;「重置」清空条件。
|
|
88
|
+
|
|
89
|
+
| 筛选字段 | 类型 | 输入限制 | 匹配规则 |
|
|
90
|
+
| :------- | :--- | :------- | :------- |
|
|
91
|
+
${filterRows}
|
|
92
|
+
|
|
93
|
+
- **查询** / **重置**:筛选卡片底部仅放查询、重置。
|
|
94
|
+
|
|
95
|
+
### 3.2 数据列表
|
|
96
|
+
|
|
97
|
+
| 列表字段 | 字段说明 | 排序 |
|
|
98
|
+
| :------- | :------- | :--- |
|
|
99
|
+
${columnRows}
|
|
100
|
+
|
|
101
|
+
- **分页**:默认 10 条;可切换 10 / 20 / 50。
|
|
102
|
+
- **行操作**:${rowActionText}。
|
|
103
|
+
${crudSections}
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## 9. 原型与 PRD 对应关系
|
|
107
|
+
|
|
108
|
+
| 原型区域 | PRD 章节 |
|
|
109
|
+
| -------- | -------- |
|
|
110
|
+
| 页眉标题 | §1 |
|
|
111
|
+
| 筛选区 | §3.1 |
|
|
112
|
+
| 列表与工具栏 | §3.2 |
|
|
113
|
+
${project.enableCrud ? '| 详情弹层 | §4 |\n| 表单弹层 | §5 |\n| 删除确认 | §6 |' : ''}
|
|
114
|
+
|
|
115
|
+
---
|
|
116
|
+
|
|
117
|
+
## 修订记录
|
|
118
|
+
|
|
119
|
+
| 版本 | 日期 | 说明 |
|
|
120
|
+
| ---- | ---- | ---- |
|
|
121
|
+
| 1.0 | ${project.batchDate || '—'} | MCP 初版生成 |
|
|
122
|
+
`;
|
|
123
|
+
}
|
|
124
|
+
export function getTemplatePath(project) {
|
|
125
|
+
const file = TEMPLATE_MAP[project.pageType];
|
|
126
|
+
const full = path.join(getTemplatesRoot(), 'template', file);
|
|
127
|
+
return fs.existsSync(full) ? full : null;
|
|
128
|
+
}
|
|
129
|
+
export function getExamplePrototypePath(project) {
|
|
130
|
+
const dir = EXAMPLE_MAP[project.pageType];
|
|
131
|
+
const full = path.join(getTemplatesRoot(), 'examples', dir, 'prototype.html');
|
|
132
|
+
return fs.existsSync(full) ? full : null;
|
|
133
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { type PrdProject } from './types.js';
|
|
2
|
+
export declare function getProjectDir(slug: string): string;
|
|
3
|
+
export declare function listProjects(): Array<{
|
|
4
|
+
slug: string;
|
|
5
|
+
pageName: string;
|
|
6
|
+
updatedAt: string;
|
|
7
|
+
}>;
|
|
8
|
+
export declare function loadProject(slug: string): PrdProject | null;
|
|
9
|
+
export declare function saveProject(project: PrdProject): {
|
|
10
|
+
slug: string;
|
|
11
|
+
prdPath: string;
|
|
12
|
+
prototypePath: string;
|
|
13
|
+
configPath: string;
|
|
14
|
+
};
|
|
15
|
+
export declare function applyOverrides(slug: string, overrides: Partial<Pick<PrdProject, 'pageName' | 'background' | 'mockRows'>> & {
|
|
16
|
+
listTitle?: string;
|
|
17
|
+
filterTitle?: string;
|
|
18
|
+
}): PrdProject | null;
|
|
19
|
+
export declare function readProjectFiles(slug: string): {
|
|
20
|
+
prd: string;
|
|
21
|
+
prototype: string;
|
|
22
|
+
config: PrdProject;
|
|
23
|
+
} | null;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { getPrototypesRoot } from '../config.js';
|
|
4
|
+
import { generatePrdMarkdown } from './prd-generator.js';
|
|
5
|
+
import { generatePrototypeHtml } from './prototype-generator.js';
|
|
6
|
+
import { PrdProjectSchema } from './types.js';
|
|
7
|
+
const CONFIG_FILE = 'project.config.json';
|
|
8
|
+
export function getProjectDir(slug) {
|
|
9
|
+
return path.join(getPrototypesRoot(), slug);
|
|
10
|
+
}
|
|
11
|
+
export function listProjects() {
|
|
12
|
+
const root = getPrototypesRoot();
|
|
13
|
+
if (!fs.existsSync(root))
|
|
14
|
+
return [];
|
|
15
|
+
return fs
|
|
16
|
+
.readdirSync(root, { withFileTypes: true })
|
|
17
|
+
.filter(d => d.isDirectory())
|
|
18
|
+
.map(d => {
|
|
19
|
+
const dir = path.join(root, d.name);
|
|
20
|
+
const configPath = path.join(dir, CONFIG_FILE);
|
|
21
|
+
let pageName = d.name;
|
|
22
|
+
if (fs.existsSync(configPath)) {
|
|
23
|
+
try {
|
|
24
|
+
const cfg = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
25
|
+
pageName = cfg.pageName || pageName;
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
/* ignore */
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
const stat = fs.statSync(dir);
|
|
32
|
+
return { slug: d.name, pageName, updatedAt: stat.mtime.toISOString() };
|
|
33
|
+
})
|
|
34
|
+
.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
|
|
35
|
+
}
|
|
36
|
+
export function loadProject(slug) {
|
|
37
|
+
const configPath = path.join(getProjectDir(slug), CONFIG_FILE);
|
|
38
|
+
if (!fs.existsSync(configPath))
|
|
39
|
+
return null;
|
|
40
|
+
return PrdProjectSchema.parse(JSON.parse(fs.readFileSync(configPath, 'utf8')));
|
|
41
|
+
}
|
|
42
|
+
export function saveProject(project) {
|
|
43
|
+
const parsed = PrdProjectSchema.parse(project);
|
|
44
|
+
const dir = getProjectDir(parsed.slug);
|
|
45
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
46
|
+
const prdPath = path.join(dir, 'prd.md');
|
|
47
|
+
const prototypePath = path.join(dir, 'prototype.html');
|
|
48
|
+
const configPath = path.join(dir, CONFIG_FILE);
|
|
49
|
+
fs.writeFileSync(configPath, JSON.stringify(parsed, null, 2), 'utf8');
|
|
50
|
+
fs.writeFileSync(prdPath, generatePrdMarkdown(parsed), 'utf8');
|
|
51
|
+
fs.writeFileSync(prototypePath, generatePrototypeHtml(parsed), 'utf8');
|
|
52
|
+
return { slug: parsed.slug, prdPath, prototypePath, configPath };
|
|
53
|
+
}
|
|
54
|
+
export function applyOverrides(slug, overrides) {
|
|
55
|
+
const project = loadProject(slug);
|
|
56
|
+
if (!project)
|
|
57
|
+
return null;
|
|
58
|
+
if (overrides.pageName)
|
|
59
|
+
project.pageName = overrides.pageName;
|
|
60
|
+
if (overrides.background)
|
|
61
|
+
project.background = overrides.background;
|
|
62
|
+
if (overrides.mockRows)
|
|
63
|
+
project.mockRows = overrides.mockRows;
|
|
64
|
+
saveProject(project);
|
|
65
|
+
return project;
|
|
66
|
+
}
|
|
67
|
+
export function readProjectFiles(slug) {
|
|
68
|
+
const project = loadProject(slug);
|
|
69
|
+
if (!project)
|
|
70
|
+
return null;
|
|
71
|
+
const dir = getProjectDir(slug);
|
|
72
|
+
return {
|
|
73
|
+
config: project,
|
|
74
|
+
prd: fs.readFileSync(path.join(dir, 'prd.md'), 'utf8'),
|
|
75
|
+
prototype: fs.readFileSync(path.join(dir, 'prototype.html'), 'utf8'),
|
|
76
|
+
};
|
|
77
|
+
}
|