prd-to-flutter 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 +149 -0
- package/bin/p2f.mjs +18 -0
- package/dist/cli.js +203 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/clean.js +35 -0
- package/dist/commands/clean.js.map +1 -0
- package/dist/commands/doctor.js +148 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/generate.js +120 -0
- package/dist/commands/generate.js.map +1 -0
- package/dist/commands/init.js +46 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/paths.js +58 -0
- package/dist/commands/paths.js.map +1 -0
- package/dist/commands/remove.js +23 -0
- package/dist/commands/remove.js.map +1 -0
- package/dist/commands/screenshots-audit.js +39 -0
- package/dist/commands/screenshots-audit.js.map +1 -0
- package/dist/commands/skills-install.js +21 -0
- package/dist/commands/skills-install.js.map +1 -0
- package/dist/commands/sync.js +84 -0
- package/dist/commands/sync.js.map +1 -0
- package/dist/commands/update.js +93 -0
- package/dist/commands/update.js.map +1 -0
- package/dist/core/existing-page-detector.js +463 -0
- package/dist/core/existing-page-detector.js.map +1 -0
- package/dist/core/fail-fast.js +50 -0
- package/dist/core/fail-fast.js.map +1 -0
- package/dist/core/feature-coverage-builder.js +667 -0
- package/dist/core/feature-coverage-builder.js.map +1 -0
- package/dist/core/flutter-project-scanner.js +393 -0
- package/dist/core/flutter-project-scanner.js.map +1 -0
- package/dist/core/implementation-plan-writer.js +190 -0
- package/dist/core/implementation-plan-writer.js.map +1 -0
- package/dist/core/logger.js +39 -0
- package/dist/core/logger.js.map +1 -0
- package/dist/core/package-info.js +33 -0
- package/dist/core/package-info.js.map +1 -0
- package/dist/core/paths.js +37 -0
- package/dist/core/paths.js.map +1 -0
- package/dist/core/playwright-capture.js +539 -0
- package/dist/core/playwright-capture.js.map +1 -0
- package/dist/core/playwright-cli.js +26 -0
- package/dist/core/playwright-cli.js.map +1 -0
- package/dist/core/playwright-install.js +40 -0
- package/dist/core/playwright-install.js.map +1 -0
- package/dist/core/prd-clone.js +131 -0
- package/dist/core/prd-clone.js.map +1 -0
- package/dist/core/prd-install.js +108 -0
- package/dist/core/prd-install.js.map +1 -0
- package/dist/core/profile.js +149 -0
- package/dist/core/profile.js.map +1 -0
- package/dist/core/project-doc-reader.js +252 -0
- package/dist/core/project-doc-reader.js.map +1 -0
- package/dist/core/report-writer.js +90 -0
- package/dist/core/report-writer.js.map +1 -0
- package/dist/core/route-name.js +60 -0
- package/dist/core/route-name.js.map +1 -0
- package/dist/core/run-context.js +160 -0
- package/dist/core/run-context.js.map +1 -0
- package/dist/core/screenshot-auditor.js +405 -0
- package/dist/core/screenshot-auditor.js.map +1 -0
- package/dist/core/screenshot-exploration-plan-writer.js +200 -0
- package/dist/core/screenshot-exploration-plan-writer.js.map +1 -0
- package/dist/core/semantic-model-builder.js +922 -0
- package/dist/core/semantic-model-builder.js.map +1 -0
- package/dist/core/skill-install.js +78 -0
- package/dist/core/skill-install.js.map +1 -0
- package/dist/core/stage-stub.js +24 -0
- package/dist/core/stage-stub.js.map +1 -0
- package/dist/core/task-index-writer.js +149 -0
- package/dist/core/task-index-writer.js.map +1 -0
- package/dist/core/update-checker.js +155 -0
- package/dist/core/update-checker.js.map +1 -0
- package/dist/core/vue-page-locator.js +748 -0
- package/dist/core/vue-page-locator.js.map +1 -0
- package/dist/core/vue-project-reader.js +116 -0
- package/dist/core/vue-project-reader.js.map +1 -0
- package/docs/artifacts-and-agent.md +203 -0
- package/docs/development.md +118 -0
- package/docs/usage.md +246 -0
- package/package.json +50 -0
- package/skills/p2f/SKILL.md +303 -0
- package/skills/p2f/references/page-layout-patterns.md +120 -0
- package/skills/p2f/references/youfi-flutter-guidelines.md +71 -0
package/README.md
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
# p2f CLI
|
|
2
|
+
|
|
3
|
+
p2f 是为 PRD 原型页面采集还原数据、生成实现计划的 CLI。它不直接生成 Dart 页面代码。
|
|
4
|
+
|
|
5
|
+
## 核心目标
|
|
6
|
+
|
|
7
|
+
- 从原型 URL 定位对应的 PRD 页面入口和相关源码范围。
|
|
8
|
+
- 使用 Playwright 采集 DOM、可访问性信息、computed styles、可见文本和运行态结构数据。
|
|
9
|
+
- 抽取页面结构、设计 token 候选、候选交互和运行态数据。
|
|
10
|
+
- 扫描 YouFi Flutter 项目,判断目标页面是否已经存在。
|
|
11
|
+
- 生成 `main.md`、截图探索计划和 `implementation-plan.md`,把 JSON、源码范围、风险点和实现线索交给 agent。
|
|
12
|
+
- CLI 只负责采集和规划,不直接生成 Dart 页面代码,不直接复制 App 资源。
|
|
13
|
+
|
|
14
|
+
## 文档
|
|
15
|
+
|
|
16
|
+
- [使用流程](docs/usage.md):初始化、常用命令、标准还原流程、generate 阶段和项目结构 profile。
|
|
17
|
+
- [产物与 Agent 规则](docs/artifacts-and-agent.md):任务目录、关键产物、截图规则、审阅顺序和实现规则。
|
|
18
|
+
- [维护与发布](docs/development.md):CLI 更新、发布、本地开发和 scanner/profile 维护。
|
|
19
|
+
|
|
20
|
+
## 环境要求
|
|
21
|
+
|
|
22
|
+
- Node.js `>= 20`
|
|
23
|
+
- Git 和公司 GitLab SSH 权限
|
|
24
|
+
- Flutter SDK
|
|
25
|
+
- YouFi Flutter 项目根目录,通常需要存在 `pubspec.yaml` 和 `lib/app`
|
|
26
|
+
- CLI 会从公司 GitLab 克隆/拉取 PRD 原型仓库到当前项目的 `.p2f-workspace/prd-source`:
|
|
27
|
+
- 远端:`git@code.nexita.net:inno/youfi/web/TradeAppPrd.git`
|
|
28
|
+
- 分支:`master`
|
|
29
|
+
|
|
30
|
+
`p2f init` 会自动安装 PRD 依赖、Playwright Chromium 和内置 p2f skill,不需要手工运行 `npx playwright install chromium`。升级 CLI 或内置 skill 变更后,可以重新执行 `p2f skills-install` 安装/更新 agent 规则。
|
|
31
|
+
|
|
32
|
+
## 安装
|
|
33
|
+
|
|
34
|
+
从公司 GitLab 仓库全局安装,建议使用 tag 固定版本:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
npm install -g git+ssh://git@code.nexita.net/inno/youfi/client/prd-to-flutter.git#v0.1.0
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
本地开发见 [维护与发布](docs/development.md)。
|
|
41
|
+
|
|
42
|
+
## 快速开始
|
|
43
|
+
|
|
44
|
+
在 YouFi Flutter 项目根目录执行,也就是包含 `pubspec.yaml` 和 `lib/app` 的目录:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
p2f init
|
|
48
|
+
p2f sync
|
|
49
|
+
p2f generate "<页面链接>"
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
如果需要人工指定页面名:
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
p2f generate --page "订单详情" "https://example.com/prototype/order-detail"
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
生成后读取:
|
|
59
|
+
|
|
60
|
+
```text
|
|
61
|
+
.p2f-workspace/pages/<task-name>/main.md
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
再按 `main.md` 中的路径审阅 `cli/analysis/feature-coverage.md`、`cli/analysis/screenshot-exploration-plan.md`、`cli/analysis/implementation-plan.md` 等产物,让 agent 打开 live prototype 补齐 `agent/screenshots/` 后再实现 Flutter 页面。
|
|
65
|
+
|
|
66
|
+
完整流程见 [使用流程](docs/usage.md)。
|
|
67
|
+
|
|
68
|
+
## 常用命令
|
|
69
|
+
|
|
70
|
+
| 命令 | 说明 |
|
|
71
|
+
| --- | --- |
|
|
72
|
+
| `p2f init` | 创建 p2f 工作区,准备 PRD 源码本地副本、原型依赖、Playwright Chromium 和内置 p2f skill。 |
|
|
73
|
+
| `p2f sync` | 从 PRD 远端 `origin/master` 拉取最新代码,并覆盖当前项目的 `.p2f-workspace/prd-source`。 |
|
|
74
|
+
| `p2f generate "<页面链接>"` | 生成单页结构化数据、源码范围、截图探索计划和实现计划。 |
|
|
75
|
+
| `p2f paths --url "<页面链接>"` | 打印当前项目的 p2f 工作区、共享项目快照、pages 和任务目录路径。 |
|
|
76
|
+
| `p2f screenshots-audit --url "<页面链接>"` | 审计 agent 截图目录,识别重复截图和疑似截到原型平台壳层的错误截图。 |
|
|
77
|
+
| `p2f doctor` | 检查环境、skill、`.p2f-workspace/prd-source` 和 p2f 版本。 |
|
|
78
|
+
| `p2f update --check` | 检查当前 p2f 是否有新版本,不安装。 |
|
|
79
|
+
| `p2f update` | 从公司 GitLab 最新 tag 重新全局安装 p2f CLI。 |
|
|
80
|
+
|
|
81
|
+
完整命令表见 [使用流程](docs/usage.md#常用命令)。
|
|
82
|
+
|
|
83
|
+
## 项目结构 Profile
|
|
84
|
+
|
|
85
|
+
`p2f generate` 默认使用内置 `youfi-default` profile。若 Flutter App 或 PRD 原型目录移动,但框架/语法约定没有变,可以在 Flutter 项目根目录新增 `p2f.config.json` 或 `.p2f/config.json` 覆盖扫描路径。
|
|
86
|
+
|
|
87
|
+
示例:
|
|
88
|
+
|
|
89
|
+
```json
|
|
90
|
+
{
|
|
91
|
+
"profile": "youfi-default",
|
|
92
|
+
"flutter": {
|
|
93
|
+
"moduleRoots": ["lib/features"],
|
|
94
|
+
"routeFiles": {
|
|
95
|
+
"constants": ["lib/router/app_routes.dart"],
|
|
96
|
+
"pages": ["lib/router/app_pages.dart"]
|
|
97
|
+
}
|
|
98
|
+
},
|
|
99
|
+
"prototype": {
|
|
100
|
+
"subprojectCandidates": ["frontend", "prototype", "."],
|
|
101
|
+
"viewsDirs": ["src/pages", "src/views"]
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
完整配置说明见 [使用流程](docs/usage.md#项目结构-profile)。
|
|
107
|
+
|
|
108
|
+
## 任务目录
|
|
109
|
+
|
|
110
|
+
每次 `generate` 会写入:
|
|
111
|
+
|
|
112
|
+
```text
|
|
113
|
+
.p2f-workspace/pages/<task-name>/
|
|
114
|
+
main.md
|
|
115
|
+
cli/
|
|
116
|
+
source/
|
|
117
|
+
runtime/
|
|
118
|
+
analysis/
|
|
119
|
+
output/
|
|
120
|
+
agent/
|
|
121
|
+
manifest.json
|
|
122
|
+
screenshots/
|
|
123
|
+
runtime/
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
项目级快照固定写入 `.p2f-workspace/project/flutter-project.json`。完整产物说明见 [产物与 Agent 规则](docs/artifacts-and-agent.md)。
|
|
127
|
+
|
|
128
|
+
## 原则
|
|
129
|
+
|
|
130
|
+
- p2f CLI 不直接生成 Dart 页面代码,不直接复制 App 资源。
|
|
131
|
+
- agent 实现前必须先审阅 `main.md`、feature coverage、截图探索计划和 implementation plan。
|
|
132
|
+
- `cli/analysis/flutter-mapping.md` 若输出 `status=uncertain` 或 `agentReviewRequired=true`,agent 必须先复核页面是否已存在,再决定修改已有页面或新增页面。
|
|
133
|
+
- agent 必须打开 live prototype 主动探索并补齐截图,截图写入 `agent/screenshots/`。
|
|
134
|
+
- agent 生成截图、探索日志、说明文件或修改 Flutter 文件后,必须同步更新 `agent/manifest.json`。
|
|
135
|
+
- 遇到 PRD 定位失败、Playwright 采集失败、Flutter 结构扫描失败或关键数据缺失时,必须 fail-fast,不基于不完整信息继续实现。
|
|
136
|
+
|
|
137
|
+
## 常见问题
|
|
138
|
+
|
|
139
|
+
### 为什么不让 CLI 直接写 Dart?
|
|
140
|
+
|
|
141
|
+
页面还原涉及已有页面复用、模块归属、路由、主题 token、业务状态和交互细节。纯 CLI 生成 Dart 的效果不稳定,容易偏离 YouFi 项目规范。p2f 的职责是准备还原数据和实现计划,真正实现由 agent 根据数据和项目规范完成。
|
|
142
|
+
|
|
143
|
+
### 为什么需要 Playwright?
|
|
144
|
+
|
|
145
|
+
PRD 原型页面的真实结构、样式和交互状态只有运行后才能看到。CLI 使用 Playwright 打开真实页面、等待渲染稳定并读取 DOM/样式/文本;截图和交互分支由 agent 后续在 live page 中主动探索。
|
|
146
|
+
|
|
147
|
+
### `.p2f-workspace` 是否需要提交?
|
|
148
|
+
|
|
149
|
+
不建议提交。它是本地运行产物、截图、分析报告和页面目录,已经加入 `.gitignore`。
|
package/bin/p2f.mjs
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Thin launcher. Real entrypoint lives in dist/cli.js (built) or src/cli.ts (dev via tsx).
|
|
3
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
4
|
+
import { dirname, resolve } from 'node:path';
|
|
5
|
+
import { existsSync } from 'node:fs';
|
|
6
|
+
|
|
7
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
8
|
+
const compiled = resolve(here, '..', 'dist', 'cli.js');
|
|
9
|
+
|
|
10
|
+
if (!existsSync(compiled)) {
|
|
11
|
+
console.error(
|
|
12
|
+
'[p2f] dist/cli.js not found. Run `npm run build` in the p2f project first.',
|
|
13
|
+
);
|
|
14
|
+
process.exit(2);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const mod = await import(pathToFileURL(compiled).href);
|
|
18
|
+
await mod.main(process.argv.slice(2));
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { generateCommand } from './commands/generate.js';
|
|
3
|
+
import { doctorCommand } from './commands/doctor.js';
|
|
4
|
+
import { initCommand } from './commands/init.js';
|
|
5
|
+
import { syncCommand } from './commands/sync.js';
|
|
6
|
+
import { skillsInstallCommand } from './commands/skills-install.js';
|
|
7
|
+
import { pathsCommand } from './commands/paths.js';
|
|
8
|
+
import { cleanCommand } from './commands/clean.js';
|
|
9
|
+
import { updateCommand } from './commands/update.js';
|
|
10
|
+
import { screenshotsAuditCommand } from './commands/screenshots-audit.js';
|
|
11
|
+
import { removeCommand } from './commands/remove.js';
|
|
12
|
+
import { logger } from './core/logger.js';
|
|
13
|
+
import { readPackageInfo } from './core/package-info.js';
|
|
14
|
+
import { routeNameFromUrl } from './core/route-name.js';
|
|
15
|
+
export async function main(argv) {
|
|
16
|
+
const program = new Command();
|
|
17
|
+
const pkg = readPackageInfo();
|
|
18
|
+
program
|
|
19
|
+
.name('p2f')
|
|
20
|
+
.description('为 PRD 原型页面采集 Flutter 还原数据并生成实现计划;不直接生成 Dart 页面代码。')
|
|
21
|
+
.version(pkg.version)
|
|
22
|
+
.showHelpAfterError();
|
|
23
|
+
program
|
|
24
|
+
.command('generate')
|
|
25
|
+
.description('生成单个页面的结构化数据、JSON、源码范围、截图探索计划和实现计划;不会直接截图或生成 Dart。')
|
|
26
|
+
.argument('[url-or-page]', '原型页面 URL;兼容旧用法时也可以是页面名称。')
|
|
27
|
+
.argument('[prototype-url]', '原型页面 URL(兼容旧用法:p2f generate "<页面名>" "<页面链接>")。')
|
|
28
|
+
.option('--page <name>', '可选页面名称;不提供时从 URL 路由提取。')
|
|
29
|
+
.option('--url <url>', '原型页面 URL。')
|
|
30
|
+
.option('--workspace <path>', 'Flutter 项目根目录,默认当前目录或向上查找 pubspec.yaml。')
|
|
31
|
+
.action(async (urlOrPageArg, urlArg, opts) => {
|
|
32
|
+
const resolved = resolveGenerateArgs({
|
|
33
|
+
urlOrPageArg,
|
|
34
|
+
urlArg,
|
|
35
|
+
pageOption: opts.page,
|
|
36
|
+
urlOption: opts.url,
|
|
37
|
+
});
|
|
38
|
+
if (!resolved.ok) {
|
|
39
|
+
logger.error(`${resolved.message}\n` +
|
|
40
|
+
' 用法:p2f generate "<页面链接>"\n' +
|
|
41
|
+
' 可选:p2f generate --page "<页面名>" "<页面链接>"\n' +
|
|
42
|
+
' 兼容:p2f generate "<页面名>" "<页面链接>"');
|
|
43
|
+
process.exitCode = 2;
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
const exitCode = await generateCommand({
|
|
47
|
+
pageName: resolved.pageName,
|
|
48
|
+
prototypeUrl: resolved.prototypeUrl,
|
|
49
|
+
workspace: opts.workspace,
|
|
50
|
+
});
|
|
51
|
+
process.exitCode = exitCode;
|
|
52
|
+
});
|
|
53
|
+
program
|
|
54
|
+
.command('doctor')
|
|
55
|
+
.description('检查 git、node、playwright、flutter、skill、.p2f-workspace/prd-source 和 p2f 版本。')
|
|
56
|
+
.option('--workspace <path>', 'Flutter 项目根目录。')
|
|
57
|
+
.option('--offline', '跳过网络检查,只检查本地状态。', false)
|
|
58
|
+
.action(async (opts) => {
|
|
59
|
+
const exitCode = await doctorCommand({
|
|
60
|
+
workspace: opts.workspace,
|
|
61
|
+
offline: Boolean(opts.offline),
|
|
62
|
+
});
|
|
63
|
+
process.exitCode = exitCode;
|
|
64
|
+
});
|
|
65
|
+
program
|
|
66
|
+
.command('init')
|
|
67
|
+
.description('在当前项目创建 p2f 工作区,并准备 PRD 源码本地副本、依赖、Playwright Chromium 和内置 p2f skill。')
|
|
68
|
+
.option('--workspace <path>', 'Flutter 项目根目录。')
|
|
69
|
+
.action(async (opts) => {
|
|
70
|
+
const exitCode = await initCommand({
|
|
71
|
+
workspace: opts.workspace,
|
|
72
|
+
});
|
|
73
|
+
process.exitCode = exitCode;
|
|
74
|
+
});
|
|
75
|
+
program
|
|
76
|
+
.command('sync')
|
|
77
|
+
.description('从 PRD 远端 origin/master 拉取最新代码,并覆盖当前项目的 .p2f-workspace/prd-source;不会 push。')
|
|
78
|
+
.option('--workspace <path>', 'Flutter 项目根目录。')
|
|
79
|
+
.action(async (opts) => {
|
|
80
|
+
const exitCode = await syncCommand({
|
|
81
|
+
workspace: opts.workspace,
|
|
82
|
+
});
|
|
83
|
+
process.exitCode = exitCode;
|
|
84
|
+
});
|
|
85
|
+
program
|
|
86
|
+
.command('paths')
|
|
87
|
+
.description('打印当前项目的 p2f 工作区、PRD 源码、pages 和任务目录路径。')
|
|
88
|
+
.option('--workspace <path>', 'Flutter 项目根目录。')
|
|
89
|
+
.option('--page <name>', '可选页面名,用于展示任务目录。')
|
|
90
|
+
.option('--url <url>', '可选原型页面 URL,用于解析任务目录。')
|
|
91
|
+
.option('--json', '输出机器可读 JSON。', false)
|
|
92
|
+
.action(async (opts) => {
|
|
93
|
+
const exitCode = await pathsCommand({
|
|
94
|
+
workspace: opts.workspace,
|
|
95
|
+
pageName: opts.page,
|
|
96
|
+
prototypeUrl: opts.url,
|
|
97
|
+
json: Boolean(opts.json),
|
|
98
|
+
});
|
|
99
|
+
process.exitCode = exitCode;
|
|
100
|
+
});
|
|
101
|
+
program
|
|
102
|
+
.command('screenshots-audit')
|
|
103
|
+
.description('审计 agent 截图目录,识别重复截图和疑似截到原型平台壳层的错误截图。')
|
|
104
|
+
.option('--workspace <path>', 'Flutter 项目根目录。')
|
|
105
|
+
.option('--page <name>', '可选页面名,用于定位任务目录。')
|
|
106
|
+
.option('--url <url>', '可选原型页面 URL,用于解析任务目录。')
|
|
107
|
+
.option('--json', '输出机器可读 JSON。', false)
|
|
108
|
+
.action(async (opts) => {
|
|
109
|
+
const exitCode = await screenshotsAuditCommand({
|
|
110
|
+
workspace: opts.workspace,
|
|
111
|
+
pageName: opts.page,
|
|
112
|
+
prototypeUrl: opts.url,
|
|
113
|
+
json: Boolean(opts.json),
|
|
114
|
+
});
|
|
115
|
+
process.exitCode = exitCode;
|
|
116
|
+
});
|
|
117
|
+
program
|
|
118
|
+
.command('clean [task-name]')
|
|
119
|
+
.description('删除指定页面目录;使用 --all 删除全部 .p2f-workspace/pages。')
|
|
120
|
+
.option('--workspace <path>', 'Flutter 项目根目录。')
|
|
121
|
+
.option('--task <name>', '只删除指定任务页面目录。')
|
|
122
|
+
.option('--url <url>', '从原型页面 URL 解析任务目录名。')
|
|
123
|
+
.option('--all', '删除全部页面目录。', false)
|
|
124
|
+
.action(async (taskName, opts) => {
|
|
125
|
+
const exitCode = await cleanCommand({
|
|
126
|
+
workspace: opts.workspace,
|
|
127
|
+
all: Boolean(opts.all),
|
|
128
|
+
taskArg: taskName,
|
|
129
|
+
task: opts.task,
|
|
130
|
+
prototypeUrl: opts.url,
|
|
131
|
+
});
|
|
132
|
+
process.exitCode = exitCode;
|
|
133
|
+
});
|
|
134
|
+
program
|
|
135
|
+
.command('remove')
|
|
136
|
+
.description('删除当前项目里的 .p2f-workspace,以及 .claude/.codex 下的 p2f skill。')
|
|
137
|
+
.option('--workspace <path>', 'Flutter 项目根目录。')
|
|
138
|
+
.action(async (opts) => {
|
|
139
|
+
const exitCode = await removeCommand({
|
|
140
|
+
workspace: opts.workspace,
|
|
141
|
+
});
|
|
142
|
+
process.exitCode = exitCode;
|
|
143
|
+
});
|
|
144
|
+
program
|
|
145
|
+
.command('update')
|
|
146
|
+
.description('从 GitLab 最新 tag 重新全局安装 p2f CLI。')
|
|
147
|
+
.option('--check', '只检查是否有新版本,不安装。', false)
|
|
148
|
+
.option('--force', '即使已经是最新版本也重新安装。', false)
|
|
149
|
+
.option('--json', '输出机器可读 JSON。', false)
|
|
150
|
+
.option('--repo <git-url>', '临时指定 p2f CLI 的 GitLab 安装来源,默认读取包内配置或 P2F_UPDATE_REPO。')
|
|
151
|
+
.action(async (opts) => {
|
|
152
|
+
const exitCode = await updateCommand({
|
|
153
|
+
check: Boolean(opts.check),
|
|
154
|
+
force: Boolean(opts.force),
|
|
155
|
+
json: Boolean(opts.json),
|
|
156
|
+
repo: opts.repo,
|
|
157
|
+
});
|
|
158
|
+
process.exitCode = exitCode;
|
|
159
|
+
});
|
|
160
|
+
program
|
|
161
|
+
.command('skills-install')
|
|
162
|
+
.description('把当前 CLI 自带的 p2f skill 安装/更新到 <workspace>/.claude/skills/p2f 和 <workspace>/.codex/skills/p2f。')
|
|
163
|
+
.option('--workspace <path>', '目标项目根目录,默认当前目录。')
|
|
164
|
+
.action(async (opts) => {
|
|
165
|
+
const exitCode = await skillsInstallCommand({
|
|
166
|
+
workspace: opts.workspace,
|
|
167
|
+
});
|
|
168
|
+
process.exitCode = exitCode;
|
|
169
|
+
});
|
|
170
|
+
await program.parseAsync(argv, { from: 'user' });
|
|
171
|
+
}
|
|
172
|
+
function resolveGenerateArgs(input) {
|
|
173
|
+
let prototypeUrl = input.urlOption;
|
|
174
|
+
let pageName = input.pageOption;
|
|
175
|
+
if (!prototypeUrl && input.urlArg) {
|
|
176
|
+
// Backward-compatible positional form:
|
|
177
|
+
// p2f generate "<page name>" "<url>"
|
|
178
|
+
prototypeUrl = input.urlArg;
|
|
179
|
+
pageName ??= input.urlOrPageArg;
|
|
180
|
+
}
|
|
181
|
+
else if (!prototypeUrl && input.urlOrPageArg) {
|
|
182
|
+
// New preferred form:
|
|
183
|
+
// p2f generate "<url>"
|
|
184
|
+
prototypeUrl = input.urlOrPageArg;
|
|
185
|
+
}
|
|
186
|
+
else if (prototypeUrl && !pageName && input.urlOrPageArg) {
|
|
187
|
+
// Optional form:
|
|
188
|
+
// p2f generate "<page name>" --url "<url>"
|
|
189
|
+
pageName = input.urlOrPageArg;
|
|
190
|
+
}
|
|
191
|
+
if (!prototypeUrl) {
|
|
192
|
+
return { ok: false, message: '必须提供原型页面 URL。' };
|
|
193
|
+
}
|
|
194
|
+
pageName = pageName?.trim() || routeNameFromUrl(prototypeUrl) || undefined;
|
|
195
|
+
if (!pageName) {
|
|
196
|
+
return {
|
|
197
|
+
ok: false,
|
|
198
|
+
message: '未提供页面名称,且无法从 URL 路由中提取名称;请使用 --page "<页面名>"。',
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
return { ok: true, pageName, prototypeUrl };
|
|
202
|
+
}
|
|
203
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { existsSync, rmSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { logger } from '../core/logger.js';
|
|
4
|
+
import { buildWorkspacePaths } from '../core/paths.js';
|
|
5
|
+
import { deriveTaskName, slugifyName } from '../core/route-name.js';
|
|
6
|
+
export async function cleanCommand(opts) {
|
|
7
|
+
logger.stage('Stage: clean-pages');
|
|
8
|
+
const paths = buildWorkspacePaths(opts.workspace);
|
|
9
|
+
const hasTask = Boolean(opts.taskArg || opts.task || opts.prototypeUrl);
|
|
10
|
+
if (opts.all && hasTask) {
|
|
11
|
+
logger.error('`p2f clean --all` 不能同时指定页面名称、--task 或 --url。');
|
|
12
|
+
return 2;
|
|
13
|
+
}
|
|
14
|
+
if (!opts.all && !hasTask) {
|
|
15
|
+
logger.error('必须指定要清理的页面目录名称,或使用 --all 清理全部页面目录。\n' +
|
|
16
|
+
' 用法:p2f clean <页面目录名称>\n' +
|
|
17
|
+
' 或者:p2f clean --url "<页面链接>"\n' +
|
|
18
|
+
' 全部:p2f clean --all');
|
|
19
|
+
return 2;
|
|
20
|
+
}
|
|
21
|
+
const taskName = opts.prototypeUrl
|
|
22
|
+
? deriveTaskName({ prototypeUrl: opts.prototypeUrl })
|
|
23
|
+
: opts.taskArg || opts.task
|
|
24
|
+
? slugifyName((opts.taskArg || opts.task))
|
|
25
|
+
: undefined;
|
|
26
|
+
const target = opts.all ? paths.pagesDir : join(paths.pagesDir, taskName);
|
|
27
|
+
if (!existsSync(target)) {
|
|
28
|
+
logger.info(`pages already clean: ${target}`);
|
|
29
|
+
return 0;
|
|
30
|
+
}
|
|
31
|
+
rmSync(target, { recursive: true, force: true });
|
|
32
|
+
logger.success(`cleaned: ${target}`);
|
|
33
|
+
return 0;
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=clean.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"clean.js","sourceRoot":"","sources":["../../src/commands/clean.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAC7C,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAC3C,OAAO,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AACvD,OAAO,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAUpE,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,IAAkB;IACnD,MAAM,CAAC,KAAK,CAAC,oBAAoB,CAAC,CAAC;IACnC,MAAM,KAAK,GAAG,mBAAmB,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IAClD,MAAM,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,OAAO,IAAI,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,YAAY,CAAC,CAAC;IAExE,IAAI,IAAI,CAAC,GAAG,IAAI,OAAO,EAAE,CAAC;QACxB,MAAM,CAAC,KAAK,CAAC,8CAA8C,CAAC,CAAC;QAC7D,OAAO,CAAC,CAAC;IACX,CAAC;IAED,IAAI,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,OAAO,EAAE,CAAC;QAC1B,MAAM,CAAC,KAAK,CACV,sCAAsC;YACpC,2BAA2B;YAC3B,iCAAiC;YACjC,sBAAsB,CACzB,CAAC;QACF,OAAO,CAAC,CAAC;IACX,CAAC;IAED,MAAM,QAAQ,GAAG,IAAI,CAAC,YAAY;QAChC,CAAC,CAAC,cAAc,CAAC,EAAE,YAAY,EAAE,IAAI,CAAC,YAAY,EAAE,CAAC;QACrD,CAAC,CAAC,IAAI,CAAC,OAAO,IAAI,IAAI,CAAC,IAAI;YACzB,CAAC,CAAC,WAAW,CAAC,CAAC,IAAI,CAAC,OAAO,IAAI,IAAI,CAAC,IAAI,CAAE,CAAC;YAC3C,CAAC,CAAC,SAAS,CAAC;IAChB,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,EAAE,QAAS,CAAC,CAAC;IAE3E,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;QACxB,MAAM,CAAC,IAAI,CAAC,wBAAwB,MAAM,EAAE,CAAC,CAAC;QAC9C,OAAO,CAAC,CAAC;IACX,CAAC;IAED,MAAM,CAAC,MAAM,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IACjD,MAAM,CAAC,OAAO,CAAC,YAAY,MAAM,EAAE,CAAC,CAAC;IACrC,OAAO,CAAC,CAAC;AACX,CAAC"}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { execa } from 'execa';
|
|
4
|
+
import { logger } from '../core/logger.js';
|
|
5
|
+
import { buildWorkspacePaths, PROTOTYPE_BRANCH, PROTOTYPE_REMOTE } from '../core/paths.js';
|
|
6
|
+
import { checkPrdFreshness } from '../core/prd-clone.js';
|
|
7
|
+
import { listSkillTargets } from '../core/skill-install.js';
|
|
8
|
+
import { checkForUpdates } from '../core/update-checker.js';
|
|
9
|
+
import { playwrightCliCommand } from '../core/playwright-cli.js';
|
|
10
|
+
export async function doctorCommand(opts) {
|
|
11
|
+
const paths = buildWorkspacePaths(opts.workspace);
|
|
12
|
+
const checks = [];
|
|
13
|
+
const offline = Boolean(opts.offline);
|
|
14
|
+
logger.stage('Stage: environment-checks');
|
|
15
|
+
checks.push(await probeVersion('node', ['--version']));
|
|
16
|
+
checks.push(await probeVersion('git', ['--version']));
|
|
17
|
+
checks.push(await probeVersion('flutter', ['--version'], true));
|
|
18
|
+
checks.push(await probeVersion(playwrightCliCommand(['--version']), true));
|
|
19
|
+
logger.stage('Stage: skill-check');
|
|
20
|
+
for (const target of listSkillTargets(paths.root)) {
|
|
21
|
+
checks.push(probeSkillInstalled(target));
|
|
22
|
+
}
|
|
23
|
+
logger.stage('Stage: prd-source-check');
|
|
24
|
+
const prototypeExists = existsSync(paths.prdSourceDir);
|
|
25
|
+
checks.push({
|
|
26
|
+
label: 'prd source',
|
|
27
|
+
ok: prototypeExists,
|
|
28
|
+
detail: prototypeExists
|
|
29
|
+
? paths.prdSourceDir
|
|
30
|
+
: `${paths.prdSourceDir} · run \`p2f init\` to clone`,
|
|
31
|
+
});
|
|
32
|
+
if (prototypeExists && !offline) {
|
|
33
|
+
checks.push(await probePrdFreshness(paths.prdSourceDir));
|
|
34
|
+
}
|
|
35
|
+
else if (prototypeExists && offline) {
|
|
36
|
+
checks.push({
|
|
37
|
+
label: 'prd repo up-to-date',
|
|
38
|
+
ok: true,
|
|
39
|
+
detail: 'skipped by --offline',
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
logger.stage('Stage: update-check');
|
|
43
|
+
checks.push(await probeP2fVersion(offline));
|
|
44
|
+
// Print summary.
|
|
45
|
+
let allGreen = true;
|
|
46
|
+
for (const c of checks) {
|
|
47
|
+
if (c.ok) {
|
|
48
|
+
logger.success(`ok · ${c.label} — ${c.detail}`);
|
|
49
|
+
}
|
|
50
|
+
else if (c.blocking === false) {
|
|
51
|
+
logger.warn(`warn · ${c.label} — ${c.detail}`);
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
logger.warn(`miss · ${c.label} — ${c.detail}`);
|
|
55
|
+
allGreen = false;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
logger.info(`prd repo remote: ${PROTOTYPE_REMOTE}`);
|
|
59
|
+
logger.info(`prd repo branch: ${PROTOTYPE_BRANCH}`);
|
|
60
|
+
return allGreen ? 0 : 1;
|
|
61
|
+
}
|
|
62
|
+
function probeSkillInstalled(target) {
|
|
63
|
+
// Bucket label by which agent dir owns it (.claude vs .codex).
|
|
64
|
+
const tag = target.includes('/.codex/') ? '.codex' : '.claude';
|
|
65
|
+
const label = `skill installed (${tag})`;
|
|
66
|
+
const skillFile = join(target, 'SKILL.md');
|
|
67
|
+
if (existsSync(skillFile)) {
|
|
68
|
+
return { label, ok: true, detail: target };
|
|
69
|
+
}
|
|
70
|
+
return {
|
|
71
|
+
label,
|
|
72
|
+
ok: false,
|
|
73
|
+
detail: `${target} · run \`p2f skills-install\` to install`,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
async function probeVersion(command, argsOrSoft = [], soft = false) {
|
|
77
|
+
const cmd = typeof command === 'string'
|
|
78
|
+
? { command, args: argsOrSoft, display: `${command} ${argsOrSoft.join(' ')}` }
|
|
79
|
+
: command;
|
|
80
|
+
if (typeof argsOrSoft === 'boolean')
|
|
81
|
+
soft = argsOrSoft;
|
|
82
|
+
try {
|
|
83
|
+
const { stdout } = await execa(cmd.command, cmd.args, { stdout: 'pipe', stderr: 'pipe' });
|
|
84
|
+
return { label: cmd.display ?? `${cmd.command} ${cmd.args.join(' ')}`, ok: true, detail: stdout.split(/\r?\n/)[0] ?? '' };
|
|
85
|
+
}
|
|
86
|
+
catch (err) {
|
|
87
|
+
const msg = err.shortMessage ?? err.message;
|
|
88
|
+
return {
|
|
89
|
+
label: `${cmd.display ?? `${cmd.command} ${cmd.args.join(' ')}`}${soft ? ' (optional)' : ''}`,
|
|
90
|
+
ok: false,
|
|
91
|
+
detail: msg,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
async function probePrdFreshness(prototypeDir) {
|
|
96
|
+
const label = 'prd repo up-to-date';
|
|
97
|
+
if (!process.env.GIT_SSH_COMMAND) {
|
|
98
|
+
process.env.GIT_SSH_COMMAND = 'ssh -o ConnectTimeout=5 -o BatchMode=yes';
|
|
99
|
+
}
|
|
100
|
+
try {
|
|
101
|
+
const f = await checkPrdFreshness(prototypeDir);
|
|
102
|
+
if (f.upToDate) {
|
|
103
|
+
return {
|
|
104
|
+
label,
|
|
105
|
+
ok: true,
|
|
106
|
+
detail: `Already up to date · ${f.localCommit.slice(0, 7)} (origin/${PROTOTYPE_BRANCH})`,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
return {
|
|
110
|
+
label,
|
|
111
|
+
ok: false,
|
|
112
|
+
detail: `local ${f.localCommit.slice(0, 7)} != remote ${f.remoteCommit.slice(0, 7)} ` +
|
|
113
|
+
`(behind ${f.behind}, ahead ${f.ahead}) · run \`p2f sync\` to update`,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
catch (err) {
|
|
117
|
+
const msg = err.shortMessage ?? err.message;
|
|
118
|
+
return { label, ok: false, detail: `freshness check failed: ${msg}` };
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
async function probeP2fVersion(offline) {
|
|
122
|
+
const label = 'p2f version';
|
|
123
|
+
if (offline) {
|
|
124
|
+
return { label, ok: true, detail: 'update check skipped by --offline' };
|
|
125
|
+
}
|
|
126
|
+
try {
|
|
127
|
+
const update = await checkForUpdates();
|
|
128
|
+
if (!update.updateAvailable) {
|
|
129
|
+
return { label, ok: true, detail: `v${update.currentVersion} · latest` };
|
|
130
|
+
}
|
|
131
|
+
return {
|
|
132
|
+
label,
|
|
133
|
+
ok: false,
|
|
134
|
+
blocking: false,
|
|
135
|
+
detail: `current v${update.currentVersion}, latest ${update.latestTag} · run \`p2f update\``,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
catch (err) {
|
|
139
|
+
const msg = err.shortMessage ?? err.message;
|
|
140
|
+
return {
|
|
141
|
+
label,
|
|
142
|
+
ok: false,
|
|
143
|
+
blocking: false,
|
|
144
|
+
detail: `update check failed: ${msg}`,
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
//# sourceMappingURL=doctor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"doctor.js","sourceRoot":"","sources":["../../src/commands/doctor.ts"],"names":[],"mappings":"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"}
|