i18-fe-automator-beta 2.1.6 → 2.1.8
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 +101 -21
- package/dist/esm/index.mjs +101 -21
- 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 命令展示用)
|
|
@@ -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");
|
|
@@ -2875,6 +2877,8 @@ async function selectPlan(plans, keyword) {
|
|
|
2875
2877
|
message: "请选择计划",
|
|
2876
2878
|
name: "plan",
|
|
2877
2879
|
type: "rawlist",
|
|
2880
|
+
// 到列表边界停止, 不循环回第一条
|
|
2881
|
+
loop: false,
|
|
2878
2882
|
choices: plans.map((p) => ({
|
|
2879
2883
|
name: `#${p.id} ${p.title} [${p.begin} ~ ${p.end}] (${
|
|
2880
2884
|
p.stories ?? 0
|
|
@@ -2950,7 +2954,11 @@ async function showPlanDetail(zentaosid, selected) {
|
|
|
2950
2954
|
);
|
|
2951
2955
|
return {
|
|
2952
2956
|
ID: s.id,
|
|
2953
|
-
|
|
2957
|
+
// console.table单元格不换行, 超长标题截断(完整标题可用 story detail 查看)
|
|
2958
|
+
标题:
|
|
2959
|
+
s.title.length > STORY_TITLE_LIMIT
|
|
2960
|
+
? `${s.title.slice(0, STORY_TITLE_LIMIT)}...`
|
|
2961
|
+
: s.title,
|
|
2954
2962
|
前端工时: isFront ? s.webestimate : "-",
|
|
2955
2963
|
后端工时: Number(s.estimate) > 0 ? s.estimate : "-",
|
|
2956
2964
|
poollist: s.poollist || "-",
|
|
@@ -2965,9 +2973,64 @@ async function showPlanDetail(zentaosid, selected) {
|
|
|
2965
2973
|
return stories;
|
|
2966
2974
|
}
|
|
2967
2975
|
|
|
2968
|
-
//
|
|
2976
|
+
// 按导出范围过滤需求: front=有前端工时 | back=有后端工时 | all=全部
|
|
2977
|
+
function filterStoriesByScope(stories, scope) {
|
|
2978
|
+
return stories.filter((s) => {
|
|
2979
|
+
if (scope === "front") return Number(s.webestimate) > 0;
|
|
2980
|
+
if (scope === "back") return Number(s.estimate) > 0;
|
|
2981
|
+
return true;
|
|
2982
|
+
});
|
|
2983
|
+
}
|
|
2984
|
+
|
|
2985
|
+
// 导出前下一步: 列出范围内需求供勾选(「全选」与子项父子联动, 复用 CheckboxAllPrompt)
|
|
2986
|
+
// 返回勾选的需求数组; 未勾选任何项返回[](调用方据此跳过导出)
|
|
2987
|
+
async function selectExportStories(stories, scope) {
|
|
2988
|
+
const scoped = filterStoriesByScope(stories, scope);
|
|
2989
|
+
// 工时展示跟随导出范围: front只展示前端工时 / back只展示后端工时 / all全展示
|
|
2990
|
+
const hoursOf = (s) => {
|
|
2991
|
+
const w = Number(s.webestimate) > 0 ? `${s.webestimate}h` : "-";
|
|
2992
|
+
const e = Number(s.estimate) > 0 ? `${s.estimate}h` : "-";
|
|
2993
|
+
if (scope === "front") return `(前端${w})`;
|
|
2994
|
+
if (scope === "back") return `(后端${e})`;
|
|
2995
|
+
return `(前端${w}/后端${e})`;
|
|
2996
|
+
};
|
|
2997
|
+
// prompt 注册幂等, 与 upload.js 各自使用前注册
|
|
2998
|
+
inquirer.registerPrompt("checkbox-all", CheckboxAllPrompt);
|
|
2999
|
+
// 需求选项拼接序号, 与数字键选择对应(按2选第一个需求...); 「全选」不占序号, 固定首项无前缀
|
|
3000
|
+
const choices = [
|
|
3001
|
+
{
|
|
3002
|
+
name: `全选(共${scoped.length}条)`,
|
|
3003
|
+
value: SELECT_ALL,
|
|
3004
|
+
short: "全选",
|
|
3005
|
+
// 默认全选(与全部子项联动一致)
|
|
3006
|
+
checked: true,
|
|
3007
|
+
},
|
|
3008
|
+
...scoped.map((s, index) => ({
|
|
3009
|
+
name: `${index + 1}. #${s.id} ${s.title} ${hoursOf(s)}`,
|
|
3010
|
+
value: String(s.id),
|
|
3011
|
+
short: `#${s.id}`,
|
|
3012
|
+
checked: true,
|
|
3013
|
+
})),
|
|
3014
|
+
];
|
|
3015
|
+
const { picked } = await inquirer.prompt([
|
|
3016
|
+
{
|
|
3017
|
+
message: `请勾选要导出的需求(空格选择, 回车确认; 「全选」与单个需求联动)`,
|
|
3018
|
+
name: "picked",
|
|
3019
|
+
type: "checkbox-all",
|
|
3020
|
+
// 到列表边界停止, 不循环回第一条
|
|
3021
|
+
loop: false,
|
|
3022
|
+
choices,
|
|
3023
|
+
},
|
|
3024
|
+
]);
|
|
3025
|
+
// 全选勾上时返回值含哨兵值, 一并忽略后按ID过滤
|
|
3026
|
+
const ids = new Set(picked.filter((v) => v !== SELECT_ALL));
|
|
3027
|
+
return scoped.filter((s) => ids.has(String(s.id)));
|
|
3028
|
+
}
|
|
3029
|
+
|
|
3030
|
+
// 导出动作(plan收尾交互使用): 按范围过滤需求(交互勾选时再按pickedIds收窄), 拉详情并产出 .zentao-<计划名>/ + 工作区
|
|
2969
3031
|
// scope: front=有前端工时的需求 | back=有后端工时的需求 | all=全部
|
|
2970
|
-
|
|
3032
|
+
// pickedIds: 交互勾选的需求ID集合(Set); 未传(--export直通一键场景)为范围内全量
|
|
3033
|
+
async function exportPlanStories(zentaosid, selected, scope, pickedIds) {
|
|
2971
3034
|
const scopeLabel =
|
|
2972
3035
|
scope === "front" ? "前端" : scope === "back" ? "后端" : "全部";
|
|
2973
3036
|
Loading$1.start("拉取计划详情中...");
|
|
@@ -2975,12 +3038,10 @@ async function exportPlanStories(zentaosid, selected, scope) {
|
|
|
2975
3038
|
zentaosid,
|
|
2976
3039
|
selected.id
|
|
2977
3040
|
);
|
|
2978
|
-
//
|
|
2979
|
-
const stories = allStories.filter(
|
|
2980
|
-
|
|
2981
|
-
|
|
2982
|
-
return true;
|
|
2983
|
-
});
|
|
3041
|
+
// 按导出范围过滤需求, 再按勾选ID收窄(pickedIds未传=范围内全量)
|
|
3042
|
+
const stories = filterStoriesByScope(allStories, scope).filter(
|
|
3043
|
+
(s) => !pickedIds || pickedIds.has(String(s.id))
|
|
3044
|
+
);
|
|
2984
3045
|
Loading$1.succeed(
|
|
2985
3046
|
`拉取计划详情成功(${scopeLabel}${stories.length}/${allStories.length}条)`
|
|
2986
3047
|
);
|
|
@@ -3018,16 +3079,23 @@ async function exportPlanStories(zentaosid, selected, scope) {
|
|
|
3018
3079
|
Loading$1.succeed(`拉取需求详情完成(${done}/${stories.length})`);
|
|
3019
3080
|
// 需求目录: .zentao-<计划名>(多计划互不混目录)
|
|
3020
3081
|
const zentaoDir = path.join(process.cwd(), zentaoDirNameOf(plan));
|
|
3021
|
-
// 同名taskDir
|
|
3082
|
+
// 同名taskDir手术式刷新: 只删task.md与images/重写, 保留plan.md/codex报告等AI产物; 失败条目保留旧数据
|
|
3022
3083
|
stories.forEach((story) => {
|
|
3023
3084
|
if (failed.has(String(story.id))) return;
|
|
3024
3085
|
const dir = path.join(zentaoDir, "tasks", taskDirOf(story));
|
|
3025
3086
|
if (isExistPath(dir)) {
|
|
3026
|
-
|
|
3027
|
-
|
|
3087
|
+
const taskMd = path.join(dir, "task.md");
|
|
3088
|
+
if (isExistPath(taskMd)) fs.rmSync(taskMd, { force: true });
|
|
3089
|
+
const imagesDir = path.join(dir, "images");
|
|
3090
|
+
if (isExistPath(imagesDir)) {
|
|
3091
|
+
fs.rmSync(imagesDir, { recursive: true, force: true });
|
|
3092
|
+
}
|
|
3093
|
+
console.log(
|
|
3094
|
+
chalk.yellow(`同名任务目录已刷新(保留plan/报告): ${taskDirOf(story)}`)
|
|
3095
|
+
);
|
|
3028
3096
|
}
|
|
3029
3097
|
});
|
|
3030
|
-
// 图片本地化到 tasks/<taskDir>/images/(
|
|
3098
|
+
// 图片本地化到 tasks/<taskDir>/images/(images/已删全量重下, 失败黄字不阻塞, 引用降级远程url)
|
|
3031
3099
|
const storyImages = await downloadStoryImages({
|
|
3032
3100
|
stories,
|
|
3033
3101
|
storyDetails,
|
|
@@ -3053,11 +3121,13 @@ async function exportPlanStories(zentaosid, selected, scope) {
|
|
|
3053
3121
|
scope === "back" || scope === "all"
|
|
3054
3122
|
? collectPoolProjects(stories, "back")
|
|
3055
3123
|
: [];
|
|
3056
|
-
|
|
3057
|
-
|
|
3058
|
-
|
|
3059
|
-
|
|
3124
|
+
// 无pool时仍生成单目录工作区(只含需求目录自身, 打开仍能看manifest与task.md), 黄字提示补维护
|
|
3125
|
+
if (!frontProjects.length && !backProjects.length) {
|
|
3126
|
+
console.log(
|
|
3127
|
+
chalk.yellow("本范围需求均未维护poollist, 工作区仅含需求目录, 维护后重跑可补齐项目")
|
|
3128
|
+
);
|
|
3060
3129
|
}
|
|
3130
|
+
writeCodeWorkspace(zentaoDir, plan.title, frontProjects, backProjects);
|
|
3061
3131
|
}
|
|
3062
3132
|
|
|
3063
3133
|
/**
|
|
@@ -4000,7 +4070,7 @@ async function storyMain(action, cliOpts) {
|
|
|
4000
4070
|
});
|
|
4001
4071
|
return;
|
|
4002
4072
|
}
|
|
4003
|
-
// plan: 产品选择 -> 计划选取 -> 展示 ->
|
|
4073
|
+
// plan: 产品选择 -> 计划选取 -> 展示 -> 导出询问(交互场景勾选需求, --export直通一键场景)
|
|
4004
4074
|
const session = await ensureZentaoSession(cliOpts);
|
|
4005
4075
|
await session.request(async (zentaosid) => {
|
|
4006
4076
|
// 位置参数纯数字 = 产品计划ID(productplan-browse-<ID>.html 中的ID), 等同 --product 直通
|
|
@@ -4044,7 +4114,17 @@ async function storyMain(action, cliOpts) {
|
|
|
4044
4114
|
]));
|
|
4045
4115
|
}
|
|
4046
4116
|
if (exportChoice !== "none") {
|
|
4047
|
-
|
|
4117
|
+
// 下一步(仅交互场景): 列出范围内需求勾选要导出的(「全选」与单个需求互斥); --export直通为范围内全量
|
|
4118
|
+
let pickedIds = null;
|
|
4119
|
+
if (cliOpts.export === undefined) {
|
|
4120
|
+
const pickedStories = await selectExportStories(stories, exportChoice);
|
|
4121
|
+
if (!pickedStories.length) {
|
|
4122
|
+
console.log(chalk.yellow("未勾选需求, 跳过导出"));
|
|
4123
|
+
return;
|
|
4124
|
+
}
|
|
4125
|
+
pickedIds = new Set(pickedStories.map((s) => String(s.id)));
|
|
4126
|
+
}
|
|
4127
|
+
await exportPlanStories(zentaosid, selected, exportChoice, pickedIds);
|
|
4048
4128
|
}
|
|
4049
4129
|
});
|
|
4050
4130
|
}
|
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 命令展示用)
|
|
@@ -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");
|
|
@@ -2853,6 +2855,8 @@ async function selectPlan(plans, keyword) {
|
|
|
2853
2855
|
message: "请选择计划",
|
|
2854
2856
|
name: "plan",
|
|
2855
2857
|
type: "rawlist",
|
|
2858
|
+
// 到列表边界停止, 不循环回第一条
|
|
2859
|
+
loop: false,
|
|
2856
2860
|
choices: plans.map((p) => ({
|
|
2857
2861
|
name: `#${p.id} ${p.title} [${p.begin} ~ ${p.end}] (${
|
|
2858
2862
|
p.stories ?? 0
|
|
@@ -2928,7 +2932,11 @@ async function showPlanDetail(zentaosid, selected) {
|
|
|
2928
2932
|
);
|
|
2929
2933
|
return {
|
|
2930
2934
|
ID: s.id,
|
|
2931
|
-
|
|
2935
|
+
// console.table单元格不换行, 超长标题截断(完整标题可用 story detail 查看)
|
|
2936
|
+
标题:
|
|
2937
|
+
s.title.length > STORY_TITLE_LIMIT
|
|
2938
|
+
? `${s.title.slice(0, STORY_TITLE_LIMIT)}...`
|
|
2939
|
+
: s.title,
|
|
2932
2940
|
前端工时: isFront ? s.webestimate : "-",
|
|
2933
2941
|
后端工时: Number(s.estimate) > 0 ? s.estimate : "-",
|
|
2934
2942
|
poollist: s.poollist || "-",
|
|
@@ -2943,9 +2951,64 @@ async function showPlanDetail(zentaosid, selected) {
|
|
|
2943
2951
|
return stories;
|
|
2944
2952
|
}
|
|
2945
2953
|
|
|
2946
|
-
//
|
|
2954
|
+
// 按导出范围过滤需求: front=有前端工时 | back=有后端工时 | all=全部
|
|
2955
|
+
function filterStoriesByScope(stories, scope) {
|
|
2956
|
+
return stories.filter((s) => {
|
|
2957
|
+
if (scope === "front") return Number(s.webestimate) > 0;
|
|
2958
|
+
if (scope === "back") return Number(s.estimate) > 0;
|
|
2959
|
+
return true;
|
|
2960
|
+
});
|
|
2961
|
+
}
|
|
2962
|
+
|
|
2963
|
+
// 导出前下一步: 列出范围内需求供勾选(「全选」与子项父子联动, 复用 CheckboxAllPrompt)
|
|
2964
|
+
// 返回勾选的需求数组; 未勾选任何项返回[](调用方据此跳过导出)
|
|
2965
|
+
async function selectExportStories(stories, scope) {
|
|
2966
|
+
const scoped = filterStoriesByScope(stories, scope);
|
|
2967
|
+
// 工时展示跟随导出范围: front只展示前端工时 / back只展示后端工时 / all全展示
|
|
2968
|
+
const hoursOf = (s) => {
|
|
2969
|
+
const w = Number(s.webestimate) > 0 ? `${s.webestimate}h` : "-";
|
|
2970
|
+
const e = Number(s.estimate) > 0 ? `${s.estimate}h` : "-";
|
|
2971
|
+
if (scope === "front") return `(前端${w})`;
|
|
2972
|
+
if (scope === "back") return `(后端${e})`;
|
|
2973
|
+
return `(前端${w}/后端${e})`;
|
|
2974
|
+
};
|
|
2975
|
+
// prompt 注册幂等, 与 upload.js 各自使用前注册
|
|
2976
|
+
inquirer.registerPrompt("checkbox-all", CheckboxAllPrompt);
|
|
2977
|
+
// 需求选项拼接序号, 与数字键选择对应(按2选第一个需求...); 「全选」不占序号, 固定首项无前缀
|
|
2978
|
+
const choices = [
|
|
2979
|
+
{
|
|
2980
|
+
name: `全选(共${scoped.length}条)`,
|
|
2981
|
+
value: SELECT_ALL,
|
|
2982
|
+
short: "全选",
|
|
2983
|
+
// 默认全选(与全部子项联动一致)
|
|
2984
|
+
checked: true,
|
|
2985
|
+
},
|
|
2986
|
+
...scoped.map((s, index) => ({
|
|
2987
|
+
name: `${index + 1}. #${s.id} ${s.title} ${hoursOf(s)}`,
|
|
2988
|
+
value: String(s.id),
|
|
2989
|
+
short: `#${s.id}`,
|
|
2990
|
+
checked: true,
|
|
2991
|
+
})),
|
|
2992
|
+
];
|
|
2993
|
+
const { picked } = await inquirer.prompt([
|
|
2994
|
+
{
|
|
2995
|
+
message: `请勾选要导出的需求(空格选择, 回车确认; 「全选」与单个需求联动)`,
|
|
2996
|
+
name: "picked",
|
|
2997
|
+
type: "checkbox-all",
|
|
2998
|
+
// 到列表边界停止, 不循环回第一条
|
|
2999
|
+
loop: false,
|
|
3000
|
+
choices,
|
|
3001
|
+
},
|
|
3002
|
+
]);
|
|
3003
|
+
// 全选勾上时返回值含哨兵值, 一并忽略后按ID过滤
|
|
3004
|
+
const ids = new Set(picked.filter((v) => v !== SELECT_ALL));
|
|
3005
|
+
return scoped.filter((s) => ids.has(String(s.id)));
|
|
3006
|
+
}
|
|
3007
|
+
|
|
3008
|
+
// 导出动作(plan收尾交互使用): 按范围过滤需求(交互勾选时再按pickedIds收窄), 拉详情并产出 .zentao-<计划名>/ + 工作区
|
|
2947
3009
|
// scope: front=有前端工时的需求 | back=有后端工时的需求 | all=全部
|
|
2948
|
-
|
|
3010
|
+
// pickedIds: 交互勾选的需求ID集合(Set); 未传(--export直通一键场景)为范围内全量
|
|
3011
|
+
async function exportPlanStories(zentaosid, selected, scope, pickedIds) {
|
|
2949
3012
|
const scopeLabel =
|
|
2950
3013
|
scope === "front" ? "前端" : scope === "back" ? "后端" : "全部";
|
|
2951
3014
|
Loading.start("拉取计划详情中...");
|
|
@@ -2953,12 +3016,10 @@ async function exportPlanStories(zentaosid, selected, scope) {
|
|
|
2953
3016
|
zentaosid,
|
|
2954
3017
|
selected.id
|
|
2955
3018
|
);
|
|
2956
|
-
//
|
|
2957
|
-
const stories = allStories.filter(
|
|
2958
|
-
|
|
2959
|
-
|
|
2960
|
-
return true;
|
|
2961
|
-
});
|
|
3019
|
+
// 按导出范围过滤需求, 再按勾选ID收窄(pickedIds未传=范围内全量)
|
|
3020
|
+
const stories = filterStoriesByScope(allStories, scope).filter(
|
|
3021
|
+
(s) => !pickedIds || pickedIds.has(String(s.id))
|
|
3022
|
+
);
|
|
2962
3023
|
Loading.succeed(
|
|
2963
3024
|
`拉取计划详情成功(${scopeLabel}${stories.length}/${allStories.length}条)`
|
|
2964
3025
|
);
|
|
@@ -2996,16 +3057,23 @@ async function exportPlanStories(zentaosid, selected, scope) {
|
|
|
2996
3057
|
Loading.succeed(`拉取需求详情完成(${done}/${stories.length})`);
|
|
2997
3058
|
// 需求目录: .zentao-<计划名>(多计划互不混目录)
|
|
2998
3059
|
const zentaoDir = path.join(process.cwd(), zentaoDirNameOf(plan));
|
|
2999
|
-
// 同名taskDir
|
|
3060
|
+
// 同名taskDir手术式刷新: 只删task.md与images/重写, 保留plan.md/codex报告等AI产物; 失败条目保留旧数据
|
|
3000
3061
|
stories.forEach((story) => {
|
|
3001
3062
|
if (failed.has(String(story.id))) return;
|
|
3002
3063
|
const dir = path.join(zentaoDir, "tasks", taskDirOf(story));
|
|
3003
3064
|
if (isExistPath(dir)) {
|
|
3004
|
-
|
|
3005
|
-
|
|
3065
|
+
const taskMd = path.join(dir, "task.md");
|
|
3066
|
+
if (isExistPath(taskMd)) fs.rmSync(taskMd, { force: true });
|
|
3067
|
+
const imagesDir = path.join(dir, "images");
|
|
3068
|
+
if (isExistPath(imagesDir)) {
|
|
3069
|
+
fs.rmSync(imagesDir, { recursive: true, force: true });
|
|
3070
|
+
}
|
|
3071
|
+
console.log(
|
|
3072
|
+
chalk.yellow(`同名任务目录已刷新(保留plan/报告): ${taskDirOf(story)}`)
|
|
3073
|
+
);
|
|
3006
3074
|
}
|
|
3007
3075
|
});
|
|
3008
|
-
// 图片本地化到 tasks/<taskDir>/images/(
|
|
3076
|
+
// 图片本地化到 tasks/<taskDir>/images/(images/已删全量重下, 失败黄字不阻塞, 引用降级远程url)
|
|
3009
3077
|
const storyImages = await downloadStoryImages({
|
|
3010
3078
|
stories,
|
|
3011
3079
|
storyDetails,
|
|
@@ -3031,11 +3099,13 @@ async function exportPlanStories(zentaosid, selected, scope) {
|
|
|
3031
3099
|
scope === "back" || scope === "all"
|
|
3032
3100
|
? collectPoolProjects(stories, "back")
|
|
3033
3101
|
: [];
|
|
3034
|
-
|
|
3035
|
-
|
|
3036
|
-
|
|
3037
|
-
|
|
3102
|
+
// 无pool时仍生成单目录工作区(只含需求目录自身, 打开仍能看manifest与task.md), 黄字提示补维护
|
|
3103
|
+
if (!frontProjects.length && !backProjects.length) {
|
|
3104
|
+
console.log(
|
|
3105
|
+
chalk.yellow("本范围需求均未维护poollist, 工作区仅含需求目录, 维护后重跑可补齐项目")
|
|
3106
|
+
);
|
|
3038
3107
|
}
|
|
3108
|
+
writeCodeWorkspace(zentaoDir, plan.title, frontProjects, backProjects);
|
|
3039
3109
|
}
|
|
3040
3110
|
|
|
3041
3111
|
/**
|
|
@@ -3978,7 +4048,7 @@ async function storyMain(action, cliOpts) {
|
|
|
3978
4048
|
});
|
|
3979
4049
|
return;
|
|
3980
4050
|
}
|
|
3981
|
-
// plan: 产品选择 -> 计划选取 -> 展示 ->
|
|
4051
|
+
// plan: 产品选择 -> 计划选取 -> 展示 -> 导出询问(交互场景勾选需求, --export直通一键场景)
|
|
3982
4052
|
const session = await ensureZentaoSession(cliOpts);
|
|
3983
4053
|
await session.request(async (zentaosid) => {
|
|
3984
4054
|
// 位置参数纯数字 = 产品计划ID(productplan-browse-<ID>.html 中的ID), 等同 --product 直通
|
|
@@ -4022,7 +4092,17 @@ async function storyMain(action, cliOpts) {
|
|
|
4022
4092
|
]));
|
|
4023
4093
|
}
|
|
4024
4094
|
if (exportChoice !== "none") {
|
|
4025
|
-
|
|
4095
|
+
// 下一步(仅交互场景): 列出范围内需求勾选要导出的(「全选」与单个需求互斥); --export直通为范围内全量
|
|
4096
|
+
let pickedIds = null;
|
|
4097
|
+
if (cliOpts.export === undefined) {
|
|
4098
|
+
const pickedStories = await selectExportStories(stories, exportChoice);
|
|
4099
|
+
if (!pickedStories.length) {
|
|
4100
|
+
console.log(chalk.yellow("未勾选需求, 跳过导出"));
|
|
4101
|
+
return;
|
|
4102
|
+
}
|
|
4103
|
+
pickedIds = new Set(pickedStories.map((s) => String(s.id)));
|
|
4104
|
+
}
|
|
4105
|
+
await exportPlanStories(zentaosid, selected, exportChoice, pickedIds);
|
|
4026
4106
|
}
|
|
4027
4107
|
});
|
|
4028
4108
|
}
|