prd-workflow-cli 1.1.31 → 1.2.6
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/.agent/workflows/prd-b1-planning-draft.md +68 -0
- package/.agent/workflows/prd-b2-planning-breakdown.md +44 -0
- package/.agent/workflows/prd-c1-a2ui-guide.md +245 -0
- package/.agent/workflows/prd-c1-requirement-list.md +131 -815
- package/.agent/workflows/prd-p0-project-info.md +44 -0
- package/.antigravity/rules.md +112 -7
- package/.cursorrules +189 -8
- package/a2ui-viewer/index.html +460 -0
- package/bin/prd-cli.js +11 -0
- package/commands/a2ui-server.js +69 -0
- package/commands/init.js +35 -0
- package/commands/upgrade.js +6 -0
- package/package.json +3 -2
|
@@ -0,0 +1,460 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="zh-CN">
|
|
3
|
+
|
|
4
|
+
<head>
|
|
5
|
+
<meta charset="UTF-8">
|
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
7
|
+
<title>PRD A2UI 预览器</title>
|
|
8
|
+
<style>
|
|
9
|
+
:root {
|
|
10
|
+
--bg-color: #f3f4f6;
|
|
11
|
+
--panel-bg: #ffffff;
|
|
12
|
+
--text-primary: #1f2937;
|
|
13
|
+
--border-color: #e5e7eb;
|
|
14
|
+
--primary-color: #2563eb;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
body {
|
|
18
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
|
19
|
+
background-color: var(--bg-color);
|
|
20
|
+
color: var(--text-primary);
|
|
21
|
+
margin: 0;
|
|
22
|
+
padding: 20px;
|
|
23
|
+
display: flex;
|
|
24
|
+
justify-content: center;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
#app {
|
|
28
|
+
width: 100%;
|
|
29
|
+
max-width: 1200px;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/* 基础组件样式 */
|
|
33
|
+
.component-page {
|
|
34
|
+
display: flex;
|
|
35
|
+
flex-direction: column;
|
|
36
|
+
gap: 16px;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.component-panel {
|
|
40
|
+
background: var(--panel-bg);
|
|
41
|
+
border-radius: 8px;
|
|
42
|
+
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
|
43
|
+
padding: 20px;
|
|
44
|
+
border: 1px solid var(--border-color);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.component-header {
|
|
48
|
+
font-size: 1.5rem;
|
|
49
|
+
font-weight: 600;
|
|
50
|
+
margin-bottom: 16px;
|
|
51
|
+
border-bottom: 1px solid var(--border-color);
|
|
52
|
+
padding-bottom: 12px;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.component-button {
|
|
56
|
+
background-color: var(--primary-color);
|
|
57
|
+
color: white;
|
|
58
|
+
border: none;
|
|
59
|
+
padding: 8px 16px;
|
|
60
|
+
border-radius: 6px;
|
|
61
|
+
cursor: pointer;
|
|
62
|
+
font-size: 0.9rem;
|
|
63
|
+
transition: background 0.2s;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.component-button:hover {
|
|
67
|
+
background-color: #1d4ed8;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.component-input {
|
|
71
|
+
width: 100%;
|
|
72
|
+
padding: 8px 12px;
|
|
73
|
+
border: 1px solid var(--border-color);
|
|
74
|
+
border-radius: 6px;
|
|
75
|
+
margin-top: 4px;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.component-label {
|
|
79
|
+
display: block;
|
|
80
|
+
font-weight: 500;
|
|
81
|
+
margin-bottom: 4px;
|
|
82
|
+
font-size: 0.9rem;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
.component-group {
|
|
86
|
+
margin-bottom: 16px;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/* 布局组件 */
|
|
90
|
+
.layout-row {
|
|
91
|
+
display: flex;
|
|
92
|
+
gap: 16px;
|
|
93
|
+
flex-wrap: wrap;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
.layout-col {
|
|
97
|
+
flex: 1;
|
|
98
|
+
min-width: 300px;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/* 状态提示 */
|
|
102
|
+
#status {
|
|
103
|
+
position: fixed;
|
|
104
|
+
bottom: 20px;
|
|
105
|
+
right: 20px;
|
|
106
|
+
background: #333;
|
|
107
|
+
color: white;
|
|
108
|
+
padding: 8px 16px;
|
|
109
|
+
border-radius: 20px;
|
|
110
|
+
font-size: 0.8rem;
|
|
111
|
+
opacity: 0.8;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/* ========== 架构图组件样式 ========== */
|
|
115
|
+
|
|
116
|
+
/* 图表容器 */
|
|
117
|
+
.component-diagram {
|
|
118
|
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
119
|
+
border-radius: 12px;
|
|
120
|
+
padding: 24px;
|
|
121
|
+
min-height: 300px;
|
|
122
|
+
position: relative;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
.diagram-title {
|
|
126
|
+
color: white;
|
|
127
|
+
font-size: 1.5rem;
|
|
128
|
+
font-weight: 600;
|
|
129
|
+
margin-bottom: 20px;
|
|
130
|
+
text-align: center;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
.diagram-content {
|
|
134
|
+
display: flex;
|
|
135
|
+
flex-direction: column;
|
|
136
|
+
gap: 20px;
|
|
137
|
+
align-items: center;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/* 模块方框 */
|
|
141
|
+
.component-box {
|
|
142
|
+
background: white;
|
|
143
|
+
border-radius: 8px;
|
|
144
|
+
padding: 16px 24px;
|
|
145
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
|
146
|
+
text-align: center;
|
|
147
|
+
min-width: 120px;
|
|
148
|
+
transition: transform 0.2s, box-shadow 0.2s;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
.component-box:hover {
|
|
152
|
+
transform: translateY(-2px);
|
|
153
|
+
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.2);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
.box-title {
|
|
157
|
+
font-weight: 600;
|
|
158
|
+
font-size: 1rem;
|
|
159
|
+
color: #1f2937;
|
|
160
|
+
margin-bottom: 4px;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
.box-desc {
|
|
164
|
+
font-size: 0.8rem;
|
|
165
|
+
color: #6b7280;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/* 分组容器 */
|
|
169
|
+
.component-group-diagram {
|
|
170
|
+
background: rgba(255, 255, 255, 0.1);
|
|
171
|
+
border: 2px dashed rgba(255, 255, 255, 0.3);
|
|
172
|
+
border-radius: 12px;
|
|
173
|
+
padding: 16px;
|
|
174
|
+
width: 100%;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
.group-title {
|
|
178
|
+
color: rgba(255, 255, 255, 0.9);
|
|
179
|
+
font-size: 0.9rem;
|
|
180
|
+
font-weight: 500;
|
|
181
|
+
margin-bottom: 12px;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
.group-content {
|
|
185
|
+
display: flex;
|
|
186
|
+
flex-wrap: wrap;
|
|
187
|
+
gap: 12px;
|
|
188
|
+
justify-content: center;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/* 连接线/箭头 */
|
|
192
|
+
.component-arrow {
|
|
193
|
+
display: flex;
|
|
194
|
+
align-items: center;
|
|
195
|
+
justify-content: center;
|
|
196
|
+
color: rgba(255, 255, 255, 0.8);
|
|
197
|
+
font-size: 1.5rem;
|
|
198
|
+
padding: 8px 0;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
.arrow-label {
|
|
202
|
+
font-size: 0.75rem;
|
|
203
|
+
margin-left: 8px;
|
|
204
|
+
color: rgba(255, 255, 255, 0.7);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/* 层级容器 */
|
|
208
|
+
.component-layer {
|
|
209
|
+
width: 100%;
|
|
210
|
+
display: flex;
|
|
211
|
+
flex-wrap: wrap;
|
|
212
|
+
gap: 12px;
|
|
213
|
+
justify-content: center;
|
|
214
|
+
padding: 12px 0;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
.layer-title {
|
|
218
|
+
width: 100%;
|
|
219
|
+
text-align: center;
|
|
220
|
+
color: rgba(255, 255, 255, 0.7);
|
|
221
|
+
font-size: 0.8rem;
|
|
222
|
+
margin-bottom: 8px;
|
|
223
|
+
}
|
|
224
|
+
</style>
|
|
225
|
+
</head>
|
|
226
|
+
|
|
227
|
+
<body>
|
|
228
|
+
<div id="app">
|
|
229
|
+
<div style="text-align: center; padding: 50px;">
|
|
230
|
+
<h2>等待 A2UI 数据...</h2>
|
|
231
|
+
<p>请在 CLI 中生成界面数据</p>
|
|
232
|
+
</div>
|
|
233
|
+
</div>
|
|
234
|
+
<div id="status">连接中...</div>
|
|
235
|
+
|
|
236
|
+
<script>
|
|
237
|
+
// A2UI 渲染引擎
|
|
238
|
+
const Renderer = {
|
|
239
|
+
create(type, props) {
|
|
240
|
+
console.log('Creating component:', type, props);
|
|
241
|
+
const el = document.createElement('div');
|
|
242
|
+
el.className = `component-${type.toLowerCase()}`;
|
|
243
|
+
|
|
244
|
+
// 处理通用属性
|
|
245
|
+
if (props.id) el.id = props.id;
|
|
246
|
+
if (props.style) Object.assign(el.style, props.style);
|
|
247
|
+
|
|
248
|
+
// 特定组件渲染逻辑
|
|
249
|
+
switch (type) {
|
|
250
|
+
case 'Page':
|
|
251
|
+
if (props.title) {
|
|
252
|
+
const title = document.createElement('div');
|
|
253
|
+
title.className = 'component-header'; // 复用 header 样式
|
|
254
|
+
title.style.borderBottom = 'none';
|
|
255
|
+
title.style.fontSize = '2rem';
|
|
256
|
+
title.textContent = props.title;
|
|
257
|
+
el.appendChild(title);
|
|
258
|
+
}
|
|
259
|
+
return el;
|
|
260
|
+
|
|
261
|
+
case 'Panel':
|
|
262
|
+
if (props.title) {
|
|
263
|
+
const title = document.createElement('div');
|
|
264
|
+
title.className = 'component-header';
|
|
265
|
+
title.textContent = props.title;
|
|
266
|
+
el.appendChild(title);
|
|
267
|
+
}
|
|
268
|
+
return el;
|
|
269
|
+
|
|
270
|
+
case 'Button':
|
|
271
|
+
const btn = document.createElement('button');
|
|
272
|
+
btn.className = 'component-button';
|
|
273
|
+
btn.textContent = props.text || 'Button';
|
|
274
|
+
return btn;
|
|
275
|
+
|
|
276
|
+
case 'Input':
|
|
277
|
+
const group = document.createElement('div');
|
|
278
|
+
group.className = 'component-group';
|
|
279
|
+
if (props.label) {
|
|
280
|
+
const label = document.createElement('label');
|
|
281
|
+
label.className = 'component-label';
|
|
282
|
+
label.textContent = props.label;
|
|
283
|
+
group.appendChild(label);
|
|
284
|
+
}
|
|
285
|
+
const input = document.createElement('input');
|
|
286
|
+
input.className = 'component-input';
|
|
287
|
+
input.placeholder = props.placeholder || '';
|
|
288
|
+
group.appendChild(input);
|
|
289
|
+
return group;
|
|
290
|
+
|
|
291
|
+
case 'Text':
|
|
292
|
+
const p = document.createElement('p');
|
|
293
|
+
p.style.margin = '8px 0';
|
|
294
|
+
p.textContent = props.content || '';
|
|
295
|
+
return p;
|
|
296
|
+
|
|
297
|
+
case 'Row':
|
|
298
|
+
el.className = 'layout-row';
|
|
299
|
+
return el;
|
|
300
|
+
|
|
301
|
+
case 'Col':
|
|
302
|
+
el.className = 'layout-col';
|
|
303
|
+
return el;
|
|
304
|
+
|
|
305
|
+
// ========== 架构图组件 ==========
|
|
306
|
+
|
|
307
|
+
case 'Diagram':
|
|
308
|
+
el.className = 'component-diagram';
|
|
309
|
+
if (props.title) {
|
|
310
|
+
const title = document.createElement('div');
|
|
311
|
+
title.className = 'diagram-title';
|
|
312
|
+
title.textContent = props.title;
|
|
313
|
+
el.appendChild(title);
|
|
314
|
+
}
|
|
315
|
+
const content = document.createElement('div');
|
|
316
|
+
content.className = 'diagram-content';
|
|
317
|
+
el.appendChild(content);
|
|
318
|
+
el._content = content; // 用于子元素渲染
|
|
319
|
+
return el;
|
|
320
|
+
|
|
321
|
+
case 'Box':
|
|
322
|
+
el.className = 'component-box';
|
|
323
|
+
if (props.color) {
|
|
324
|
+
el.style.borderLeft = `4px solid ${props.color}`;
|
|
325
|
+
}
|
|
326
|
+
const boxTitle = document.createElement('div');
|
|
327
|
+
boxTitle.className = 'box-title';
|
|
328
|
+
boxTitle.textContent = props.title || props.text || '';
|
|
329
|
+
el.appendChild(boxTitle);
|
|
330
|
+
if (props.desc) {
|
|
331
|
+
const boxDesc = document.createElement('div');
|
|
332
|
+
boxDesc.className = 'box-desc';
|
|
333
|
+
boxDesc.textContent = props.desc;
|
|
334
|
+
el.appendChild(boxDesc);
|
|
335
|
+
}
|
|
336
|
+
return el;
|
|
337
|
+
|
|
338
|
+
case 'Arrow':
|
|
339
|
+
el.className = 'component-arrow';
|
|
340
|
+
const arrowSymbol = props.direction === 'up' ? '↑' :
|
|
341
|
+
props.direction === 'left' ? '←' :
|
|
342
|
+
props.direction === 'right' ? '→' : '↓';
|
|
343
|
+
el.textContent = arrowSymbol;
|
|
344
|
+
if (props.label) {
|
|
345
|
+
const arrowLabel = document.createElement('span');
|
|
346
|
+
arrowLabel.className = 'arrow-label';
|
|
347
|
+
arrowLabel.textContent = props.label;
|
|
348
|
+
el.appendChild(arrowLabel);
|
|
349
|
+
}
|
|
350
|
+
return el;
|
|
351
|
+
|
|
352
|
+
case 'Layer':
|
|
353
|
+
el.className = 'component-layer';
|
|
354
|
+
if (props.title) {
|
|
355
|
+
const layerTitle = document.createElement('div');
|
|
356
|
+
layerTitle.className = 'layer-title';
|
|
357
|
+
layerTitle.textContent = props.title;
|
|
358
|
+
el.appendChild(layerTitle);
|
|
359
|
+
}
|
|
360
|
+
return el;
|
|
361
|
+
|
|
362
|
+
case 'DiagramGroup':
|
|
363
|
+
el.className = 'component-group-diagram';
|
|
364
|
+
if (props.title) {
|
|
365
|
+
const groupTitle = document.createElement('div');
|
|
366
|
+
groupTitle.className = 'group-title';
|
|
367
|
+
groupTitle.textContent = props.title;
|
|
368
|
+
el.appendChild(groupTitle);
|
|
369
|
+
}
|
|
370
|
+
const groupContent = document.createElement('div');
|
|
371
|
+
groupContent.className = 'group-content';
|
|
372
|
+
el.appendChild(groupContent);
|
|
373
|
+
el._content = groupContent;
|
|
374
|
+
return el;
|
|
375
|
+
|
|
376
|
+
default:
|
|
377
|
+
console.warn('Unknown component type:', type);
|
|
378
|
+
el.textContent = `Unknown Component: ${type}`;
|
|
379
|
+
el.style.border = '1px dashed red';
|
|
380
|
+
el.style.padding = '10px';
|
|
381
|
+
el.style.color = 'red';
|
|
382
|
+
return el;
|
|
383
|
+
}
|
|
384
|
+
},
|
|
385
|
+
|
|
386
|
+
// 递归渲染树
|
|
387
|
+
renderNode(node, container) {
|
|
388
|
+
if (!node) return;
|
|
389
|
+
|
|
390
|
+
try {
|
|
391
|
+
const el = this.create(node.type || 'Unknown', node);
|
|
392
|
+
|
|
393
|
+
// 容器只有在非叶子节点时才去递归渲染 children
|
|
394
|
+
// Input, Button, Text, Box, Arrow 等通常不包含 children
|
|
395
|
+
const containerTypes = ['Page', 'Panel', 'Row', 'Col', 'Diagram', 'Layer', 'DiagramGroup'];
|
|
396
|
+
if (containerTypes.includes(node.type)) {
|
|
397
|
+
if (node.children && Array.isArray(node.children)) {
|
|
398
|
+
// Diagram 和 DiagramGroup 有特殊的内容容器
|
|
399
|
+
const targetContainer = el._content || el;
|
|
400
|
+
node.children.forEach(child => this.renderNode(child, targetContainer));
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
container.appendChild(el);
|
|
405
|
+
} catch (err) {
|
|
406
|
+
console.error('Render Error:', err, node);
|
|
407
|
+
const errEl = document.createElement('div');
|
|
408
|
+
errEl.style.color = 'red';
|
|
409
|
+
errEl.textContent = `渲染错误: ${err.message}`;
|
|
410
|
+
container.appendChild(errEl);
|
|
411
|
+
}
|
|
412
|
+
},
|
|
413
|
+
|
|
414
|
+
render(data) {
|
|
415
|
+
console.log('Rendering data:', data);
|
|
416
|
+
const app = document.getElementById('app');
|
|
417
|
+
app.innerHTML = ''; // 清空当前视图
|
|
418
|
+
|
|
419
|
+
if (!data) {
|
|
420
|
+
app.innerHTML = '<div style="text-align: center; padding: 50px;">无数据</div>';
|
|
421
|
+
return;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
this.renderNode(data, app);
|
|
425
|
+
}
|
|
426
|
+
};
|
|
427
|
+
|
|
428
|
+
// 模拟数据轮询 (真实环境应该用 WebSocket 或 SSE)
|
|
429
|
+
let lastDataStr = '';
|
|
430
|
+
|
|
431
|
+
async function checkUpdate() {
|
|
432
|
+
try {
|
|
433
|
+
const res = await fetch('/ui.json');
|
|
434
|
+
if (res.ok) {
|
|
435
|
+
const data = await res.json();
|
|
436
|
+
const currentDataStr = JSON.stringify(data);
|
|
437
|
+
|
|
438
|
+
// 只有数据变化时才重新渲染
|
|
439
|
+
if (currentDataStr !== lastDataStr) {
|
|
440
|
+
lastDataStr = currentDataStr;
|
|
441
|
+
Renderer.render(data);
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
document.getElementById('status').textContent = '已连接: 实时预览中';
|
|
445
|
+
document.getElementById('status').style.background = '#059669';
|
|
446
|
+
}
|
|
447
|
+
} catch (e) {
|
|
448
|
+
console.log('Waiting for data...', e);
|
|
449
|
+
document.getElementById('status').textContent = `连接失败: ${e.message}`;
|
|
450
|
+
document.getElementById('status').style.background = '#dc2626';
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
// 初始化
|
|
455
|
+
setInterval(checkUpdate, 1000); // 每秒轮询一次
|
|
456
|
+
checkUpdate();
|
|
457
|
+
</script>
|
|
458
|
+
</body>
|
|
459
|
+
|
|
460
|
+
</html>
|
package/bin/prd-cli.js
CHANGED
|
@@ -100,6 +100,17 @@ program
|
|
|
100
100
|
require('../commands/upgrade')(options);
|
|
101
101
|
});
|
|
102
102
|
|
|
103
|
+
// A2UI 预览服务
|
|
104
|
+
program
|
|
105
|
+
.command('ui')
|
|
106
|
+
.description('启动 A2UI 界面预览服务')
|
|
107
|
+
.option('-p, --port <number>', '指定端口号', '3333')
|
|
108
|
+
.action((options) => {
|
|
109
|
+
const A2UIServer = require('../commands/a2ui-server');
|
|
110
|
+
const server = new A2UIServer(options.port);
|
|
111
|
+
server.start();
|
|
112
|
+
});
|
|
113
|
+
|
|
103
114
|
// 帮助信息增强
|
|
104
115
|
program.on('--help', () => {
|
|
105
116
|
console.log('');
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
const http = require('http');
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const chalk = require('chalk');
|
|
5
|
+
const { exec } = require('child_process');
|
|
6
|
+
|
|
7
|
+
class A2UIServer {
|
|
8
|
+
constructor(port = 3333) {
|
|
9
|
+
this.port = port;
|
|
10
|
+
this.viewerPath = path.join(__dirname, '../a2ui-viewer');
|
|
11
|
+
this.projectPath = process.cwd();
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
start() {
|
|
15
|
+
const server = http.createServer((req, res) => {
|
|
16
|
+
// 处理 CORS
|
|
17
|
+
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
18
|
+
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
|
|
19
|
+
|
|
20
|
+
// 路由处理
|
|
21
|
+
if (req.url === '/') {
|
|
22
|
+
this.serveFile(res, path.join(this.viewerPath, 'index.html'), 'text/html');
|
|
23
|
+
} else if (req.url === '/ui.json') {
|
|
24
|
+
// 读取项目根目录下的 a2ui-data.json
|
|
25
|
+
this.serveFile(res, path.join(this.projectPath, '.a2ui/current.json'), 'application/json');
|
|
26
|
+
} else {
|
|
27
|
+
res.writeHead(404);
|
|
28
|
+
res.end('Not found');
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
server.listen(this.port, () => {
|
|
33
|
+
console.log(chalk.green(`\n🚀 A2UI 预览服务已启动!`));
|
|
34
|
+
console.log(chalk.cyan(`👉 打开浏览器访问: http://localhost:${this.port}\n`));
|
|
35
|
+
|
|
36
|
+
// 自动打开浏览器
|
|
37
|
+
const startCommand = process.platform === 'darwin' ? 'open' :
|
|
38
|
+
process.platform === 'win32' ? 'start' : 'xdg-open';
|
|
39
|
+
exec(`${startCommand} http://localhost:${this.port}`);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
return server;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
serveFile(res, filePath, contentType) {
|
|
46
|
+
fs.readFile(filePath, (err, content) => {
|
|
47
|
+
if (err) {
|
|
48
|
+
if (err.code === 'ENOENT') {
|
|
49
|
+
// 如果数据文件不存在,返回空对象
|
|
50
|
+
if (filePath.endsWith('.json')) {
|
|
51
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
52
|
+
res.end(JSON.stringify({ type: 'Page', title: '等待数据...', children: [] }));
|
|
53
|
+
} else {
|
|
54
|
+
res.writeHead(404);
|
|
55
|
+
res.end('File not found');
|
|
56
|
+
}
|
|
57
|
+
} else {
|
|
58
|
+
res.writeHead(500);
|
|
59
|
+
res.end(`Server Error: ${err.code}`);
|
|
60
|
+
}
|
|
61
|
+
} else {
|
|
62
|
+
res.writeHead(200, { 'Content-Type': contentType });
|
|
63
|
+
res.end(content);
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
module.exports = A2UIServer;
|
package/commands/init.js
CHANGED
|
@@ -18,6 +18,21 @@ module.exports = async function (projectName) {
|
|
|
18
18
|
// 检查当前目录是否已经是 PRD 项目
|
|
19
19
|
if (isCurrentDir && await fs.pathExists(path.join(projectPath, '.prd-config.json'))) {
|
|
20
20
|
console.log(chalk.red('✗ 当前目录已经是 PRD 项目'));
|
|
21
|
+
console.log(chalk.yellow(' 如需更新规则文件,请运行: prd upgrade'));
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// ⚠️ 检查是否在已有 PRD 项目中创建子项目(常见错误)
|
|
26
|
+
if (!isCurrentDir && await fs.pathExists(path.join(process.cwd(), '.prd-config.json'))) {
|
|
27
|
+
console.log(chalk.yellow('⚠️ 警告:当前目录已经是一个 PRD 项目!'));
|
|
28
|
+
console.log(chalk.yellow(` 你正在尝试在 PRD 项目中创建子项目 "${projectName}"。`));
|
|
29
|
+
console.log('');
|
|
30
|
+
console.log(chalk.cyan(' 建议操作:'));
|
|
31
|
+
console.log(chalk.gray(' 1. 如果要在当前项目工作,直接使用 prd baseline create A0 等命令'));
|
|
32
|
+
console.log(chalk.gray(' 2. 如果确实要创建独立新项目,请先 cd 到其他目录'));
|
|
33
|
+
console.log(chalk.gray(' 3. 如果要更新规则文件,请运行: prd upgrade'));
|
|
34
|
+
console.log('');
|
|
35
|
+
console.log(chalk.red(' 已取消操作。'));
|
|
21
36
|
return;
|
|
22
37
|
}
|
|
23
38
|
|
|
@@ -267,6 +282,18 @@ module.exports = async function (projectName) {
|
|
|
267
282
|
);
|
|
268
283
|
}
|
|
269
284
|
|
|
285
|
+
// 复制 A2UI 预览器
|
|
286
|
+
const a2uiViewerDir = path.join(__dirname, '../a2ui-viewer');
|
|
287
|
+
if (await fs.pathExists(a2uiViewerDir)) {
|
|
288
|
+
await fs.copy(
|
|
289
|
+
a2uiViewerDir,
|
|
290
|
+
path.join(projectPath, 'a2ui-viewer')
|
|
291
|
+
);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// 创建 .a2ui 目录(用于临时预览数据)
|
|
295
|
+
await fs.ensureDir(path.join(projectPath, '.a2ui'));
|
|
296
|
+
|
|
270
297
|
// 创建 README
|
|
271
298
|
const readme = `# ${displayName}
|
|
272
299
|
|
|
@@ -352,9 +379,12 @@ prd plan freeze
|
|
|
352
379
|
console.log(chalk.gray(' ✓ .agent/workflows/ - PRD 工作流指引(包含所有阶段的详细步骤)'));
|
|
353
380
|
console.log(chalk.gray(' ✓ .cursorrules - Cursor AI 规则'));
|
|
354
381
|
console.log(chalk.gray(' ✓ .antigravity/ - Antigravity AI 规则'));
|
|
382
|
+
console.log(chalk.gray(' ✓ a2ui-viewer/ - A2UI 界面预览器'));
|
|
383
|
+
console.log(chalk.gray(' ✓ .a2ui/ - A2UI 临时数据目录'));
|
|
355
384
|
console.log('');
|
|
356
385
|
console.log(chalk.yellow(' 💡 现在你可以直接与 AI 助手对话,AI 已经知道如何协助你完成 PRD 流程!'));
|
|
357
386
|
console.log(chalk.gray(' 例如:告诉 AI "我要创建一个新项目的需求文档"'));
|
|
387
|
+
console.log(chalk.gray(' 启动界面预览:运行 prd ui'));
|
|
358
388
|
console.log('');
|
|
359
389
|
|
|
360
390
|
console.log(chalk.bold('📋 下一步操作(请按顺序执行):'));
|
|
@@ -375,6 +405,11 @@ prd plan freeze
|
|
|
375
405
|
console.log(' prd baseline create A0 # P0 填写完成后执行');
|
|
376
406
|
console.log('');
|
|
377
407
|
|
|
408
|
+
console.log(chalk.bold('🔄 后续更新:'));
|
|
409
|
+
console.log(chalk.gray(' 当 CLI 包有新版本时,运行以下命令同步更新项目规则:'));
|
|
410
|
+
console.log(chalk.cyan(' npm update -g prd-workflow-cli && prd upgrade'));
|
|
411
|
+
console.log('');
|
|
412
|
+
|
|
378
413
|
} catch (error) {
|
|
379
414
|
console.log(chalk.red('✗ 创建项目失败:'), error.message);
|
|
380
415
|
}
|
package/commands/upgrade.js
CHANGED
|
@@ -105,6 +105,12 @@ module.exports = async function (options = {}) {
|
|
|
105
105
|
source: '.antigravity',
|
|
106
106
|
target: '.antigravity',
|
|
107
107
|
isDir: true
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
name: 'A2UI Viewer (预览器)',
|
|
111
|
+
source: 'a2ui-viewer',
|
|
112
|
+
target: 'a2ui-viewer',
|
|
113
|
+
isDir: true
|
|
108
114
|
}
|
|
109
115
|
];
|
|
110
116
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "prd-workflow-cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.6",
|
|
4
4
|
"description": "产品需求管理规范 CLI 工具 - 基于 A→R→B→C 流程,集成 PM 确认机制和对话归档的需求管理命令行工具",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
@@ -48,6 +48,7 @@
|
|
|
48
48
|
"bin/",
|
|
49
49
|
"commands/",
|
|
50
50
|
"scripts/",
|
|
51
|
+
"a2ui-viewer/",
|
|
51
52
|
".agent/",
|
|
52
53
|
".antigravity/",
|
|
53
54
|
"templates/",
|
|
@@ -70,4 +71,4 @@
|
|
|
70
71
|
"/tests/"
|
|
71
72
|
]
|
|
72
73
|
}
|
|
73
|
-
}
|
|
74
|
+
}
|