i18-fe-automator-beta 2.1.5 → 2.1.7
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/dist/commonjs/index.js +63 -25
- package/dist/esm/index.mjs +63 -25
- package/package.json +1 -1
package/dist/commonjs/index.js
CHANGED
|
@@ -1807,6 +1807,8 @@ const CACHE_KEY = "zentao"; // .cache/zentao: { account, password, zentaosid, pr
|
|
|
1807
1807
|
|
|
1808
1808
|
// 需求列表展示与pull并发的上限
|
|
1809
1809
|
const PLAN_STORY_TABLE_LIMIT = 50;
|
|
1810
|
+
// 需求列表表格中标题的最大展示长度(console.table单元格不换行, 超长截断)
|
|
1811
|
+
const STORY_TITLE_LIMIT = 30;
|
|
1810
1812
|
const PULL_CONCURRENCY = 5;
|
|
1811
1813
|
|
|
1812
1814
|
// 任务状态中文标签(close 命令展示用)
|
|
@@ -2215,7 +2217,7 @@ async function selectProduct(zentaosid, cliOpts) {
|
|
|
2215
2217
|
name: `#${p.id} ${p.name}`,
|
|
2216
2218
|
value: p.id,
|
|
2217
2219
|
})),
|
|
2218
|
-
{ name: "
|
|
2220
|
+
{ name: "输入其他产品-计划ID(如: https://zentao.hongxinshop.com/zentao/productplan-browse-29.html 中的29)", value: "__INPUT__" },
|
|
2219
2221
|
],
|
|
2220
2222
|
},
|
|
2221
2223
|
]);
|
|
@@ -2226,11 +2228,11 @@ async function selectProduct(zentaosid, cliOpts) {
|
|
|
2226
2228
|
}
|
|
2227
2229
|
const { id } = await inquirer.prompt([
|
|
2228
2230
|
{
|
|
2229
|
-
message: "
|
|
2231
|
+
message: "请输入产品计划ID(如: https://zentao.hongxinshop.com/zentao/productplan-browse-29.html 中的29)",
|
|
2230
2232
|
name: "id",
|
|
2231
2233
|
type: "input",
|
|
2232
2234
|
validate: (val) =>
|
|
2233
|
-
/^\d+$/.test(String(val).trim()) || "
|
|
2235
|
+
/^\d+$/.test(String(val).trim()) || "请输入数字产品计划ID",
|
|
2234
2236
|
},
|
|
2235
2237
|
]);
|
|
2236
2238
|
return resolveProduct(zentaosid, id);
|
|
@@ -2752,8 +2754,8 @@ function writeCodeWorkspace(zentaoDir, planTitle, frontProjects, backProjects) {
|
|
|
2752
2754
|
/**
|
|
2753
2755
|
* plan --export 产出需求目录 .zentao-<计划名>/(产出契约见 .trae/documents/story-command-zentao.md 第4节):
|
|
2754
2756
|
* tasks/manifest.json(对象结构: 顶部projects汇总全部涉及项目 + stories数组合并去重, 同storyID覆盖title/taskDir/projects, 按storyID数值排序)
|
|
2755
|
-
* + 每条需求 tasks/<taskDir>/task.md(同名taskDir
|
|
2756
|
-
* plan.md 与 codex-*.md 是 workflow 的产物(
|
|
2757
|
+
* + 每条需求 tasks/<taskDir>/task.md(同名taskDir的task.md与images/已由 exportPlanStories 先删除, 此处全新写入)
|
|
2758
|
+
* plan.md 与 codex-*.md 是 workflow 的产物(重导时手术式刷新保留不覆盖, 但需求内容变了需AI自行判断是否重排)
|
|
2757
2759
|
*/
|
|
2758
2760
|
function writeZentaoTasks({ plan, scope, stories, storyDetails, products, failed, storyImages, zentaoDir }) {
|
|
2759
2761
|
const tasksDir = path.join(zentaoDir, "tasks");
|
|
@@ -2854,7 +2856,7 @@ function writeZentaoTasks({ plan, scope, stories, storyDetails, products, failed
|
|
|
2854
2856
|
}
|
|
2855
2857
|
}
|
|
2856
2858
|
|
|
2857
|
-
// 计划选取交互: 关键词过滤 > 命中1条自动选中 > rawlist选取
|
|
2859
|
+
// 计划选取交互: 关键词过滤(标题) > 命中1条自动选中 > rawlist选取
|
|
2858
2860
|
async function selectPlan(plans, keyword) {
|
|
2859
2861
|
if (keyword && String(keyword).trim()) {
|
|
2860
2862
|
const kw = String(keyword).trim().toLowerCase();
|
|
@@ -2899,10 +2901,15 @@ async function loadPlanList(zentaosid, productID) {
|
|
|
2899
2901
|
return plans;
|
|
2900
2902
|
}
|
|
2901
2903
|
|
|
2902
|
-
// plan动作: 选中计划后展示计划信息 + 关联需求列表(ID供detail用)
|
|
2904
|
+
// plan动作: 选中计划后展示计划信息 + 关联需求列表(ID供detail用); 返回关联需求(供调用方判断空计划)
|
|
2903
2905
|
async function showPlanDetail(zentaosid, selected) {
|
|
2904
2906
|
Loading$1.start("拉取计划详情中...");
|
|
2905
2907
|
const { plan, stories } = await fetchPlanDetail(zentaosid, selected.id);
|
|
2908
|
+
// 0条需求时详情/工时统计均无意义, 一句话结束
|
|
2909
|
+
if (!stories.length) {
|
|
2910
|
+
Loading$1.succeed(`计划 #${plan.id} ${plan.title} 无关联需求`);
|
|
2911
|
+
return stories;
|
|
2912
|
+
}
|
|
2906
2913
|
Loading$1.succeed("拉取计划详情成功");
|
|
2907
2914
|
console.log(chalk.bold("\n计划信息"));
|
|
2908
2915
|
console.table([
|
|
@@ -2945,7 +2952,11 @@ async function showPlanDetail(zentaosid, selected) {
|
|
|
2945
2952
|
);
|
|
2946
2953
|
return {
|
|
2947
2954
|
ID: s.id,
|
|
2948
|
-
|
|
2955
|
+
// console.table单元格不换行, 超长标题截断(完整标题可用 story detail 查看)
|
|
2956
|
+
标题:
|
|
2957
|
+
s.title.length > STORY_TITLE_LIMIT
|
|
2958
|
+
? `${s.title.slice(0, STORY_TITLE_LIMIT)}...`
|
|
2959
|
+
: s.title,
|
|
2949
2960
|
前端工时: isFront ? s.webestimate : "-",
|
|
2950
2961
|
后端工时: Number(s.estimate) > 0 ? s.estimate : "-",
|
|
2951
2962
|
poollist: s.poollist || "-",
|
|
@@ -2957,6 +2968,7 @@ async function showPlanDetail(zentaosid, selected) {
|
|
|
2957
2968
|
console.log(
|
|
2958
2969
|
chalk.blue("提示: ID 供 fe-it-beta story detail <需求ID> 使用(也可传需求url)")
|
|
2959
2970
|
);
|
|
2971
|
+
return stories;
|
|
2960
2972
|
}
|
|
2961
2973
|
|
|
2962
2974
|
// 导出动作(plan收尾交互使用): 按范围过滤需求, 拉详情并产出 .zentao-<计划名>/ + 工作区
|
|
@@ -3012,16 +3024,23 @@ async function exportPlanStories(zentaosid, selected, scope) {
|
|
|
3012
3024
|
Loading$1.succeed(`拉取需求详情完成(${done}/${stories.length})`);
|
|
3013
3025
|
// 需求目录: .zentao-<计划名>(多计划互不混目录)
|
|
3014
3026
|
const zentaoDir = path.join(process.cwd(), zentaoDirNameOf(plan));
|
|
3015
|
-
// 同名taskDir
|
|
3027
|
+
// 同名taskDir手术式刷新: 只删task.md与images/重写, 保留plan.md/codex报告等AI产物; 失败条目保留旧数据
|
|
3016
3028
|
stories.forEach((story) => {
|
|
3017
3029
|
if (failed.has(String(story.id))) return;
|
|
3018
3030
|
const dir = path.join(zentaoDir, "tasks", taskDirOf(story));
|
|
3019
3031
|
if (isExistPath(dir)) {
|
|
3020
|
-
|
|
3021
|
-
|
|
3032
|
+
const taskMd = path.join(dir, "task.md");
|
|
3033
|
+
if (isExistPath(taskMd)) fs.rmSync(taskMd, { force: true });
|
|
3034
|
+
const imagesDir = path.join(dir, "images");
|
|
3035
|
+
if (isExistPath(imagesDir)) {
|
|
3036
|
+
fs.rmSync(imagesDir, { recursive: true, force: true });
|
|
3037
|
+
}
|
|
3038
|
+
console.log(
|
|
3039
|
+
chalk.yellow(`同名任务目录已刷新(保留plan/报告): ${taskDirOf(story)}`)
|
|
3040
|
+
);
|
|
3022
3041
|
}
|
|
3023
3042
|
});
|
|
3024
|
-
// 图片本地化到 tasks/<taskDir>/images/(
|
|
3043
|
+
// 图片本地化到 tasks/<taskDir>/images/(images/已删全量重下, 失败黄字不阻塞, 引用降级远程url)
|
|
3025
3044
|
const storyImages = await downloadStoryImages({
|
|
3026
3045
|
stories,
|
|
3027
3046
|
storyDetails,
|
|
@@ -3047,11 +3066,13 @@ async function exportPlanStories(zentaosid, selected, scope) {
|
|
|
3047
3066
|
scope === "back" || scope === "all"
|
|
3048
3067
|
? collectPoolProjects(stories, "back")
|
|
3049
3068
|
: [];
|
|
3050
|
-
|
|
3051
|
-
|
|
3052
|
-
|
|
3053
|
-
|
|
3069
|
+
// 无pool时仍生成单目录工作区(只含需求目录自身, 打开仍能看manifest与task.md), 黄字提示补维护
|
|
3070
|
+
if (!frontProjects.length && !backProjects.length) {
|
|
3071
|
+
console.log(
|
|
3072
|
+
chalk.yellow("本范围需求均未维护poollist, 工作区仅含需求目录, 维护后重跑可补齐项目")
|
|
3073
|
+
);
|
|
3054
3074
|
}
|
|
3075
|
+
writeCodeWorkspace(zentaoDir, plan.title, frontProjects, backProjects);
|
|
3055
3076
|
}
|
|
3056
3077
|
|
|
3057
3078
|
/**
|
|
@@ -3125,7 +3146,7 @@ async function storyMain(action, cliOpts) {
|
|
|
3125
3146
|
if (!match) {
|
|
3126
3147
|
console.log(
|
|
3127
3148
|
chalk.red(
|
|
3128
|
-
"
|
|
3149
|
+
"请传入项目-需求url的ID, 如: fe-it-beta story task 2915 或 fe-it-beta story task https://zentao.hongxinshop.com/zentao/project-story-2915.html"
|
|
3129
3150
|
)
|
|
3130
3151
|
);
|
|
3131
3152
|
process.exit(1);
|
|
@@ -3725,7 +3746,7 @@ async function storyMain(action, cliOpts) {
|
|
|
3725
3746
|
if (!match) {
|
|
3726
3747
|
console.log(
|
|
3727
3748
|
chalk.red(
|
|
3728
|
-
"
|
|
3749
|
+
"请传入项目-任务-由我参与url的ID, 如: fe-it-beta story close 2915 或 fe-it-beta story close https://zentao.hongxinshop.com/zentao/project-task-2915-myinvolved.html"
|
|
3729
3750
|
)
|
|
3730
3751
|
);
|
|
3731
3752
|
process.exit(1);
|
|
@@ -3997,10 +4018,26 @@ async function storyMain(action, cliOpts) {
|
|
|
3997
4018
|
// plan: 产品选择 -> 计划选取 -> 展示 -> 导出(--export直通一键场景, 不传则收尾交互)
|
|
3998
4019
|
const session = await ensureZentaoSession(cliOpts);
|
|
3999
4020
|
await session.request(async (zentaosid) => {
|
|
4000
|
-
|
|
4021
|
+
// 位置参数纯数字 = 产品计划ID(productplan-browse-<ID>.html 中的ID), 等同 --product 直通
|
|
4022
|
+
const nameArg = String(cliOpts.name || "").trim();
|
|
4023
|
+
const productIdArg = /^\d+$/.test(nameArg) ? nameArg : "";
|
|
4024
|
+
const product = await selectProduct(zentaosid, {
|
|
4025
|
+
...cliOpts,
|
|
4026
|
+
product: cliOpts.product || productIdArg,
|
|
4027
|
+
});
|
|
4001
4028
|
const plans = await loadPlanList(zentaosid, product.id);
|
|
4002
|
-
|
|
4003
|
-
|
|
4029
|
+
// 该产品无计划时后续选取/详情/导出均无意义, 提前结束
|
|
4030
|
+
if (!plans.length) {
|
|
4031
|
+
console.log(chalk.yellow(`产品 #${product.id} ${product.name} 下无计划, 结束`));
|
|
4032
|
+
return;
|
|
4033
|
+
}
|
|
4034
|
+
// 纯数字已用作产品ID, 非数字关键词仍按计划标题过滤
|
|
4035
|
+
const selected = await selectPlan(plans, productIdArg ? "" : cliOpts.name);
|
|
4036
|
+
const stories = await showPlanDetail(zentaosid, selected);
|
|
4037
|
+
// 空计划提示已在showPlanDetail输出, 直接结束不进导出询问
|
|
4038
|
+
if (!stories.length) {
|
|
4039
|
+
return;
|
|
4040
|
+
}
|
|
4004
4041
|
// 导出范围: --export参数直通(一键场景) > 收尾交互(默认前端)
|
|
4005
4042
|
let exportChoice;
|
|
4006
4043
|
if (cliOpts.export !== undefined) {
|
|
@@ -4009,14 +4046,14 @@ async function storyMain(action, cliOpts) {
|
|
|
4009
4046
|
} else {
|
|
4010
4047
|
({ exportChoice } = await inquirer.prompt([
|
|
4011
4048
|
{
|
|
4012
|
-
message:
|
|
4049
|
+
message: `是否导出需求文件(按计划生成 .zentao-<计划名>目录, ${chalk.red("目前导出功能只适用于claude code批量建任务")})?`,
|
|
4013
4050
|
name: "exportChoice",
|
|
4014
4051
|
type: "list",
|
|
4015
4052
|
choices: [
|
|
4016
|
-
{ name: "
|
|
4053
|
+
{ name: "否", value: "none" }, // 默认第一项
|
|
4054
|
+
{ name: "导出: 前端需求", value: "front" },
|
|
4017
4055
|
{ name: "导出: 后端需求", value: "back" },
|
|
4018
4056
|
{ name: "导出: 全部需求", value: "all" },
|
|
4019
|
-
{ name: "否", value: "none" },
|
|
4020
4057
|
],
|
|
4021
4058
|
},
|
|
4022
4059
|
]));
|
|
@@ -4042,7 +4079,7 @@ function registerStoryCommand(program, { markHandled, addHelpExample }) {
|
|
|
4042
4079
|
.option("--username <name>", "禅道账号(story命令用)")
|
|
4043
4080
|
.option("--password <pwd>", "禅道密码(story命令用, 日常建议用--cache)")
|
|
4044
4081
|
.option("--cache", "凭据静默走缓存")
|
|
4045
|
-
.option("--product <id>", "
|
|
4082
|
+
.option("--product <id>", "禅道产品计划ID(plan用, 即productplan-browse-<ID>.html中的ID, 不传则交互选择)")
|
|
4046
4083
|
.option("--export <scope>", "plan导出范围: front|back|all(传了跳过收尾询问直接导出)")
|
|
4047
4084
|
.option("--type <t>", "task任务类型: front|back(传了跳过类型询问与确认, 一键创建)")
|
|
4048
4085
|
.option("--split", "task时创建父任务并自动拆分子任务(>8h, 每个8h末个补余); subtask时跳过确认直接拆")
|
|
@@ -4061,6 +4098,7 @@ function registerStoryCommand(program, { markHandled, addHelpExample }) {
|
|
|
4061
4098
|
// help示例经 helpers 注入 index.js 的 helpExamples, 自动参与列对齐
|
|
4062
4099
|
addHelpExample("fe-it-beta story login", "禅道登录, 缓存zentaosid");
|
|
4063
4100
|
addHelpExample("fe-it-beta story plan --product 87", "选计划看列表, 交互选导出");
|
|
4101
|
+
addHelpExample("fe-it-beta story plan 29", "29=产品计划ID(productplan-browse-29.html), 拉计划列表再选取");
|
|
4064
4102
|
addHelpExample("fe-it-beta story detail 需求ID", "按需求ID查看详情");
|
|
4065
4103
|
addHelpExample("fe-it-beta story task 项目ID", "选项目勾选需求, 交互建禅道任务");
|
|
4066
4104
|
addHelpExample(
|
package/dist/esm/index.mjs
CHANGED
|
@@ -1785,6 +1785,8 @@ const CACHE_KEY = "zentao"; // .cache/zentao: { account, password, zentaosid, pr
|
|
|
1785
1785
|
|
|
1786
1786
|
// 需求列表展示与pull并发的上限
|
|
1787
1787
|
const PLAN_STORY_TABLE_LIMIT = 50;
|
|
1788
|
+
// 需求列表表格中标题的最大展示长度(console.table单元格不换行, 超长截断)
|
|
1789
|
+
const STORY_TITLE_LIMIT = 30;
|
|
1788
1790
|
const PULL_CONCURRENCY = 5;
|
|
1789
1791
|
|
|
1790
1792
|
// 任务状态中文标签(close 命令展示用)
|
|
@@ -2193,7 +2195,7 @@ async function selectProduct(zentaosid, cliOpts) {
|
|
|
2193
2195
|
name: `#${p.id} ${p.name}`,
|
|
2194
2196
|
value: p.id,
|
|
2195
2197
|
})),
|
|
2196
|
-
{ name: "
|
|
2198
|
+
{ name: "输入其他产品-计划ID(如: https://zentao.hongxinshop.com/zentao/productplan-browse-29.html 中的29)", value: "__INPUT__" },
|
|
2197
2199
|
],
|
|
2198
2200
|
},
|
|
2199
2201
|
]);
|
|
@@ -2204,11 +2206,11 @@ async function selectProduct(zentaosid, cliOpts) {
|
|
|
2204
2206
|
}
|
|
2205
2207
|
const { id } = await inquirer.prompt([
|
|
2206
2208
|
{
|
|
2207
|
-
message: "
|
|
2209
|
+
message: "请输入产品计划ID(如: https://zentao.hongxinshop.com/zentao/productplan-browse-29.html 中的29)",
|
|
2208
2210
|
name: "id",
|
|
2209
2211
|
type: "input",
|
|
2210
2212
|
validate: (val) =>
|
|
2211
|
-
/^\d+$/.test(String(val).trim()) || "
|
|
2213
|
+
/^\d+$/.test(String(val).trim()) || "请输入数字产品计划ID",
|
|
2212
2214
|
},
|
|
2213
2215
|
]);
|
|
2214
2216
|
return resolveProduct(zentaosid, id);
|
|
@@ -2730,8 +2732,8 @@ function writeCodeWorkspace(zentaoDir, planTitle, frontProjects, backProjects) {
|
|
|
2730
2732
|
/**
|
|
2731
2733
|
* plan --export 产出需求目录 .zentao-<计划名>/(产出契约见 .trae/documents/story-command-zentao.md 第4节):
|
|
2732
2734
|
* tasks/manifest.json(对象结构: 顶部projects汇总全部涉及项目 + stories数组合并去重, 同storyID覆盖title/taskDir/projects, 按storyID数值排序)
|
|
2733
|
-
* + 每条需求 tasks/<taskDir>/task.md(同名taskDir
|
|
2734
|
-
* plan.md 与 codex-*.md 是 workflow 的产物(
|
|
2735
|
+
* + 每条需求 tasks/<taskDir>/task.md(同名taskDir的task.md与images/已由 exportPlanStories 先删除, 此处全新写入)
|
|
2736
|
+
* plan.md 与 codex-*.md 是 workflow 的产物(重导时手术式刷新保留不覆盖, 但需求内容变了需AI自行判断是否重排)
|
|
2735
2737
|
*/
|
|
2736
2738
|
function writeZentaoTasks({ plan, scope, stories, storyDetails, products, failed, storyImages, zentaoDir }) {
|
|
2737
2739
|
const tasksDir = path.join(zentaoDir, "tasks");
|
|
@@ -2832,7 +2834,7 @@ function writeZentaoTasks({ plan, scope, stories, storyDetails, products, failed
|
|
|
2832
2834
|
}
|
|
2833
2835
|
}
|
|
2834
2836
|
|
|
2835
|
-
// 计划选取交互: 关键词过滤 > 命中1条自动选中 > rawlist选取
|
|
2837
|
+
// 计划选取交互: 关键词过滤(标题) > 命中1条自动选中 > rawlist选取
|
|
2836
2838
|
async function selectPlan(plans, keyword) {
|
|
2837
2839
|
if (keyword && String(keyword).trim()) {
|
|
2838
2840
|
const kw = String(keyword).trim().toLowerCase();
|
|
@@ -2877,10 +2879,15 @@ async function loadPlanList(zentaosid, productID) {
|
|
|
2877
2879
|
return plans;
|
|
2878
2880
|
}
|
|
2879
2881
|
|
|
2880
|
-
// plan动作: 选中计划后展示计划信息 + 关联需求列表(ID供detail用)
|
|
2882
|
+
// plan动作: 选中计划后展示计划信息 + 关联需求列表(ID供detail用); 返回关联需求(供调用方判断空计划)
|
|
2881
2883
|
async function showPlanDetail(zentaosid, selected) {
|
|
2882
2884
|
Loading.start("拉取计划详情中...");
|
|
2883
2885
|
const { plan, stories } = await fetchPlanDetail(zentaosid, selected.id);
|
|
2886
|
+
// 0条需求时详情/工时统计均无意义, 一句话结束
|
|
2887
|
+
if (!stories.length) {
|
|
2888
|
+
Loading.succeed(`计划 #${plan.id} ${plan.title} 无关联需求`);
|
|
2889
|
+
return stories;
|
|
2890
|
+
}
|
|
2884
2891
|
Loading.succeed("拉取计划详情成功");
|
|
2885
2892
|
console.log(chalk.bold("\n计划信息"));
|
|
2886
2893
|
console.table([
|
|
@@ -2923,7 +2930,11 @@ async function showPlanDetail(zentaosid, selected) {
|
|
|
2923
2930
|
);
|
|
2924
2931
|
return {
|
|
2925
2932
|
ID: s.id,
|
|
2926
|
-
|
|
2933
|
+
// console.table单元格不换行, 超长标题截断(完整标题可用 story detail 查看)
|
|
2934
|
+
标题:
|
|
2935
|
+
s.title.length > STORY_TITLE_LIMIT
|
|
2936
|
+
? `${s.title.slice(0, STORY_TITLE_LIMIT)}...`
|
|
2937
|
+
: s.title,
|
|
2927
2938
|
前端工时: isFront ? s.webestimate : "-",
|
|
2928
2939
|
后端工时: Number(s.estimate) > 0 ? s.estimate : "-",
|
|
2929
2940
|
poollist: s.poollist || "-",
|
|
@@ -2935,6 +2946,7 @@ async function showPlanDetail(zentaosid, selected) {
|
|
|
2935
2946
|
console.log(
|
|
2936
2947
|
chalk.blue("提示: ID 供 fe-it-beta story detail <需求ID> 使用(也可传需求url)")
|
|
2937
2948
|
);
|
|
2949
|
+
return stories;
|
|
2938
2950
|
}
|
|
2939
2951
|
|
|
2940
2952
|
// 导出动作(plan收尾交互使用): 按范围过滤需求, 拉详情并产出 .zentao-<计划名>/ + 工作区
|
|
@@ -2990,16 +3002,23 @@ async function exportPlanStories(zentaosid, selected, scope) {
|
|
|
2990
3002
|
Loading.succeed(`拉取需求详情完成(${done}/${stories.length})`);
|
|
2991
3003
|
// 需求目录: .zentao-<计划名>(多计划互不混目录)
|
|
2992
3004
|
const zentaoDir = path.join(process.cwd(), zentaoDirNameOf(plan));
|
|
2993
|
-
// 同名taskDir
|
|
3005
|
+
// 同名taskDir手术式刷新: 只删task.md与images/重写, 保留plan.md/codex报告等AI产物; 失败条目保留旧数据
|
|
2994
3006
|
stories.forEach((story) => {
|
|
2995
3007
|
if (failed.has(String(story.id))) return;
|
|
2996
3008
|
const dir = path.join(zentaoDir, "tasks", taskDirOf(story));
|
|
2997
3009
|
if (isExistPath(dir)) {
|
|
2998
|
-
|
|
2999
|
-
|
|
3010
|
+
const taskMd = path.join(dir, "task.md");
|
|
3011
|
+
if (isExistPath(taskMd)) fs.rmSync(taskMd, { force: true });
|
|
3012
|
+
const imagesDir = path.join(dir, "images");
|
|
3013
|
+
if (isExistPath(imagesDir)) {
|
|
3014
|
+
fs.rmSync(imagesDir, { recursive: true, force: true });
|
|
3015
|
+
}
|
|
3016
|
+
console.log(
|
|
3017
|
+
chalk.yellow(`同名任务目录已刷新(保留plan/报告): ${taskDirOf(story)}`)
|
|
3018
|
+
);
|
|
3000
3019
|
}
|
|
3001
3020
|
});
|
|
3002
|
-
// 图片本地化到 tasks/<taskDir>/images/(
|
|
3021
|
+
// 图片本地化到 tasks/<taskDir>/images/(images/已删全量重下, 失败黄字不阻塞, 引用降级远程url)
|
|
3003
3022
|
const storyImages = await downloadStoryImages({
|
|
3004
3023
|
stories,
|
|
3005
3024
|
storyDetails,
|
|
@@ -3025,11 +3044,13 @@ async function exportPlanStories(zentaosid, selected, scope) {
|
|
|
3025
3044
|
scope === "back" || scope === "all"
|
|
3026
3045
|
? collectPoolProjects(stories, "back")
|
|
3027
3046
|
: [];
|
|
3028
|
-
|
|
3029
|
-
|
|
3030
|
-
|
|
3031
|
-
|
|
3047
|
+
// 无pool时仍生成单目录工作区(只含需求目录自身, 打开仍能看manifest与task.md), 黄字提示补维护
|
|
3048
|
+
if (!frontProjects.length && !backProjects.length) {
|
|
3049
|
+
console.log(
|
|
3050
|
+
chalk.yellow("本范围需求均未维护poollist, 工作区仅含需求目录, 维护后重跑可补齐项目")
|
|
3051
|
+
);
|
|
3032
3052
|
}
|
|
3053
|
+
writeCodeWorkspace(zentaoDir, plan.title, frontProjects, backProjects);
|
|
3033
3054
|
}
|
|
3034
3055
|
|
|
3035
3056
|
/**
|
|
@@ -3103,7 +3124,7 @@ async function storyMain(action, cliOpts) {
|
|
|
3103
3124
|
if (!match) {
|
|
3104
3125
|
console.log(
|
|
3105
3126
|
chalk.red(
|
|
3106
|
-
"
|
|
3127
|
+
"请传入项目-需求url的ID, 如: fe-it-beta story task 2915 或 fe-it-beta story task https://zentao.hongxinshop.com/zentao/project-story-2915.html"
|
|
3107
3128
|
)
|
|
3108
3129
|
);
|
|
3109
3130
|
process.exit(1);
|
|
@@ -3703,7 +3724,7 @@ async function storyMain(action, cliOpts) {
|
|
|
3703
3724
|
if (!match) {
|
|
3704
3725
|
console.log(
|
|
3705
3726
|
chalk.red(
|
|
3706
|
-
"
|
|
3727
|
+
"请传入项目-任务-由我参与url的ID, 如: fe-it-beta story close 2915 或 fe-it-beta story close https://zentao.hongxinshop.com/zentao/project-task-2915-myinvolved.html"
|
|
3707
3728
|
)
|
|
3708
3729
|
);
|
|
3709
3730
|
process.exit(1);
|
|
@@ -3975,10 +3996,26 @@ async function storyMain(action, cliOpts) {
|
|
|
3975
3996
|
// plan: 产品选择 -> 计划选取 -> 展示 -> 导出(--export直通一键场景, 不传则收尾交互)
|
|
3976
3997
|
const session = await ensureZentaoSession(cliOpts);
|
|
3977
3998
|
await session.request(async (zentaosid) => {
|
|
3978
|
-
|
|
3999
|
+
// 位置参数纯数字 = 产品计划ID(productplan-browse-<ID>.html 中的ID), 等同 --product 直通
|
|
4000
|
+
const nameArg = String(cliOpts.name || "").trim();
|
|
4001
|
+
const productIdArg = /^\d+$/.test(nameArg) ? nameArg : "";
|
|
4002
|
+
const product = await selectProduct(zentaosid, {
|
|
4003
|
+
...cliOpts,
|
|
4004
|
+
product: cliOpts.product || productIdArg,
|
|
4005
|
+
});
|
|
3979
4006
|
const plans = await loadPlanList(zentaosid, product.id);
|
|
3980
|
-
|
|
3981
|
-
|
|
4007
|
+
// 该产品无计划时后续选取/详情/导出均无意义, 提前结束
|
|
4008
|
+
if (!plans.length) {
|
|
4009
|
+
console.log(chalk.yellow(`产品 #${product.id} ${product.name} 下无计划, 结束`));
|
|
4010
|
+
return;
|
|
4011
|
+
}
|
|
4012
|
+
// 纯数字已用作产品ID, 非数字关键词仍按计划标题过滤
|
|
4013
|
+
const selected = await selectPlan(plans, productIdArg ? "" : cliOpts.name);
|
|
4014
|
+
const stories = await showPlanDetail(zentaosid, selected);
|
|
4015
|
+
// 空计划提示已在showPlanDetail输出, 直接结束不进导出询问
|
|
4016
|
+
if (!stories.length) {
|
|
4017
|
+
return;
|
|
4018
|
+
}
|
|
3982
4019
|
// 导出范围: --export参数直通(一键场景) > 收尾交互(默认前端)
|
|
3983
4020
|
let exportChoice;
|
|
3984
4021
|
if (cliOpts.export !== undefined) {
|
|
@@ -3987,14 +4024,14 @@ async function storyMain(action, cliOpts) {
|
|
|
3987
4024
|
} else {
|
|
3988
4025
|
({ exportChoice } = await inquirer.prompt([
|
|
3989
4026
|
{
|
|
3990
|
-
message:
|
|
4027
|
+
message: `是否导出需求文件(按计划生成 .zentao-<计划名>目录, ${chalk.red("目前导出功能只适用于claude code批量建任务")})?`,
|
|
3991
4028
|
name: "exportChoice",
|
|
3992
4029
|
type: "list",
|
|
3993
4030
|
choices: [
|
|
3994
|
-
{ name: "
|
|
4031
|
+
{ name: "否", value: "none" }, // 默认第一项
|
|
4032
|
+
{ name: "导出: 前端需求", value: "front" },
|
|
3995
4033
|
{ name: "导出: 后端需求", value: "back" },
|
|
3996
4034
|
{ name: "导出: 全部需求", value: "all" },
|
|
3997
|
-
{ name: "否", value: "none" },
|
|
3998
4035
|
],
|
|
3999
4036
|
},
|
|
4000
4037
|
]));
|
|
@@ -4020,7 +4057,7 @@ function registerStoryCommand(program, { markHandled, addHelpExample }) {
|
|
|
4020
4057
|
.option("--username <name>", "禅道账号(story命令用)")
|
|
4021
4058
|
.option("--password <pwd>", "禅道密码(story命令用, 日常建议用--cache)")
|
|
4022
4059
|
.option("--cache", "凭据静默走缓存")
|
|
4023
|
-
.option("--product <id>", "
|
|
4060
|
+
.option("--product <id>", "禅道产品计划ID(plan用, 即productplan-browse-<ID>.html中的ID, 不传则交互选择)")
|
|
4024
4061
|
.option("--export <scope>", "plan导出范围: front|back|all(传了跳过收尾询问直接导出)")
|
|
4025
4062
|
.option("--type <t>", "task任务类型: front|back(传了跳过类型询问与确认, 一键创建)")
|
|
4026
4063
|
.option("--split", "task时创建父任务并自动拆分子任务(>8h, 每个8h末个补余); subtask时跳过确认直接拆")
|
|
@@ -4039,6 +4076,7 @@ function registerStoryCommand(program, { markHandled, addHelpExample }) {
|
|
|
4039
4076
|
// help示例经 helpers 注入 index.js 的 helpExamples, 自动参与列对齐
|
|
4040
4077
|
addHelpExample("fe-it-beta story login", "禅道登录, 缓存zentaosid");
|
|
4041
4078
|
addHelpExample("fe-it-beta story plan --product 87", "选计划看列表, 交互选导出");
|
|
4079
|
+
addHelpExample("fe-it-beta story plan 29", "29=产品计划ID(productplan-browse-29.html), 拉计划列表再选取");
|
|
4042
4080
|
addHelpExample("fe-it-beta story detail 需求ID", "按需求ID查看详情");
|
|
4043
4081
|
addHelpExample("fe-it-beta story task 项目ID", "选项目勾选需求, 交互建禅道任务");
|
|
4044
4082
|
addHelpExample(
|