yg-team-cli 2.6.5 → 2.7.1
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/cli.js +759 -12
- package/dist/cli.js.map +1 -1
- package/dist/index.js +754 -12
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -4217,9 +4217,30 @@ Generate a complete Spec document with the following sections:
|
|
|
4217
4217
|
\u5217\u51FA\u9700\u8981\u7684\u540E\u7AEF\u6A21\u5757\u7ED3\u6784
|
|
4218
4218
|
|
|
4219
4219
|
## \u9A8C\u6536\u6807\u51C6
|
|
4220
|
-
|
|
4221
|
-
|
|
4222
|
-
|
|
4220
|
+
|
|
4221
|
+
### BDD \u573A\u666F\uFF08\u884C\u4E3A\u9A71\u52A8\u89C6\u89D2\uFF09
|
|
4222
|
+
|
|
4223
|
+
\u8BF7\u4E3A\u6BCF\u4E2A\u4E3B\u8981\u529F\u80FD\u70B9\u751F\u6210 2-4 \u4E2A BDD \u573A\u666F\uFF0C\u683C\u5F0F\u5982\u4E0B\uFF1A
|
|
4224
|
+
|
|
4225
|
+
\`\`\`markdown
|
|
4226
|
+
#### \u573A\u666F 1: [\u573A\u666F\u540D\u79F0]
|
|
4227
|
+
**\u4F5C\u4E3A** [\u7528\u6237\u89D2\u8272]
|
|
4228
|
+
**\u6211\u5E0C\u671B** [\u5B8C\u6210\u7684\u52A8\u4F5C]
|
|
4229
|
+
**\u4EE5\u4FBF** [\u5B9E\u73B0\u7684\u4EF7\u503C]
|
|
4230
|
+
|
|
4231
|
+
- [GIVEN] \u524D\u7F6E\u6761\u4EF6\u63CF\u8FF0
|
|
4232
|
+
- [WHEN] \u7528\u6237\u52A8\u4F5C\u6216 API \u8C03\u7528
|
|
4233
|
+
- [THEN] \u9884\u671F\u7ED3\u679C
|
|
4234
|
+
|
|
4235
|
+
**\u72B6\u6001\u6D41\u8F6C**:
|
|
4236
|
+
- \`\u672A\u767B\u5F55\` \u2192 \`\u767B\u5F55\u4E2D\` \u2192 \`\u5DF2\u767B\u5F55\`
|
|
4237
|
+
\`\`\`
|
|
4238
|
+
|
|
4239
|
+
\u8981\u6C42\uFF1A
|
|
4240
|
+
1. \u6BCF\u4E2A\u573A\u666F\u8986\u76D6\u4E00\u4E2A\u5B8C\u6574\u7684\u7528\u6237\u6D41\u7A0B
|
|
4241
|
+
2. Given/When/Then \u7ED3\u6784\u6E05\u6670
|
|
4242
|
+
3. \u72B6\u6001\u6D41\u8F6C\u6807\u6CE8\u660E\u786E
|
|
4243
|
+
4. \u573A\u666F\u6570\u91CF\u6839\u636E\u529F\u80FD\u590D\u6742\u5EA6\u51B3\u5B9A\uFF08\u7B80\u5355\u529F\u80FD 2-3 \u4E2A\uFF0C\u590D\u6742\u529F\u80FD 4-6 \u4E2A\uFF09
|
|
4223
4244
|
|
|
4224
4245
|
## \u91CC\u7A0B\u7891 (Milestones)
|
|
4225
4246
|
> \u6CE8: \u4F7F\u7528 \`team-cli breakdown ${featureSlug}.md\` \u62C6\u5206\u6B64 spec \u4E3A milestones \u548C todos
|
|
@@ -4234,6 +4255,7 @@ IMPORTANT:
|
|
|
4234
4255
|
3. Priority should be justified based on feature importance
|
|
4235
4256
|
4. Work estimation should be realistic
|
|
4236
4257
|
5. Extract key requirements from the PRD accurately
|
|
4258
|
+
6. BDD scenarios should cover all user flows and edge cases
|
|
4237
4259
|
`;
|
|
4238
4260
|
}
|
|
4239
4261
|
function buildSplitPrdPrompt(prdContent, screenshots, demoRepos) {
|
|
@@ -4688,10 +4710,366 @@ Temporary solution: ${answers.solution}`
|
|
|
4688
4710
|
}
|
|
4689
4711
|
});
|
|
4690
4712
|
|
|
4713
|
+
// src/lib/bdd-parser.ts
|
|
4714
|
+
var BDDParser;
|
|
4715
|
+
var init_bdd_parser = __esm({
|
|
4716
|
+
"src/lib/bdd-parser.ts"() {
|
|
4717
|
+
"use strict";
|
|
4718
|
+
init_esm_shims();
|
|
4719
|
+
BDDParser = class {
|
|
4720
|
+
static SCENARIO_PATTERN = /^####?\s*场景\s*(\d+)[::]?\s*(.+)$/;
|
|
4721
|
+
static GIVEN_PATTERN = /^\*?\s*-\s*\[GIVEN\]\s*(.+)$/;
|
|
4722
|
+
static WHEN_PATTERN = /^\*?\s*-\s*\[WHEN\]\s*(.+)$/;
|
|
4723
|
+
static THEN_PATTERN = /^\*?\s*-\s*\[THEN\]\s*(.+)$/;
|
|
4724
|
+
static STATE_PATTERN = /^\*?\s*-\s*\[STATE\]\s*(.+)$/;
|
|
4725
|
+
static ROLE_PATTERN = /^\*\*作为\*\*[::]?\s*(.+)$/i;
|
|
4726
|
+
static WANT_PATTERN = /^\*\*我希望\*\*[::]?\s*(.+)$/i;
|
|
4727
|
+
static SO_THAT_PATTERN = /^\*\*以便\*\*[::]?\s*(.+)$/i;
|
|
4728
|
+
static TRANSITION_PATTERN = /`([^`]+)`\s*→\s*`([^`]+)`/;
|
|
4729
|
+
/**
|
|
4730
|
+
* 解析 BDD 场景
|
|
4731
|
+
*/
|
|
4732
|
+
static parseScenarios(content) {
|
|
4733
|
+
const scenarios = [];
|
|
4734
|
+
const lines = content.split("\n");
|
|
4735
|
+
let currentScenario = null;
|
|
4736
|
+
let section = null;
|
|
4737
|
+
let scenarioOrder = 0;
|
|
4738
|
+
for (let i = 0; i < lines.length; i++) {
|
|
4739
|
+
const line = lines[i].trim();
|
|
4740
|
+
const lineNum = i + 1;
|
|
4741
|
+
const scenarioMatch = line.match(this.SCENARIO_PATTERN);
|
|
4742
|
+
if (scenarioMatch) {
|
|
4743
|
+
if (currentScenario && currentScenario.title) {
|
|
4744
|
+
scenarios.push(this.finalizeScenario(currentScenario, scenarioOrder));
|
|
4745
|
+
}
|
|
4746
|
+
scenarioOrder++;
|
|
4747
|
+
currentScenario = {
|
|
4748
|
+
id: `scenario-${scenarioOrder}`,
|
|
4749
|
+
title: scenarioMatch[2].trim(),
|
|
4750
|
+
order: scenarioOrder,
|
|
4751
|
+
given: [],
|
|
4752
|
+
when: [],
|
|
4753
|
+
then: [],
|
|
4754
|
+
stateTransitions: [],
|
|
4755
|
+
relatedApis: [],
|
|
4756
|
+
relatedFeatures: []
|
|
4757
|
+
};
|
|
4758
|
+
section = "title";
|
|
4759
|
+
continue;
|
|
4760
|
+
}
|
|
4761
|
+
if (line.match(/^\*\*作为\*\*[::]?/i)) {
|
|
4762
|
+
const match = line.match(this.ROLE_PATTERN);
|
|
4763
|
+
if (match && currentScenario) {
|
|
4764
|
+
currentScenario.asA = match[1].trim();
|
|
4765
|
+
}
|
|
4766
|
+
continue;
|
|
4767
|
+
}
|
|
4768
|
+
if (line.match(/^\*\*我希望\*\*[::]?/i)) {
|
|
4769
|
+
const match = line.match(this.WANT_PATTERN);
|
|
4770
|
+
if (match && currentScenario) {
|
|
4771
|
+
currentScenario.iWant = match[1].trim();
|
|
4772
|
+
}
|
|
4773
|
+
continue;
|
|
4774
|
+
}
|
|
4775
|
+
if (line.match(/^\*\*以便\*\*[::]?/i)) {
|
|
4776
|
+
const match = line.match(this.SO_THAT_PATTERN);
|
|
4777
|
+
if (match && currentScenario) {
|
|
4778
|
+
currentScenario.soThat = match[1].trim();
|
|
4779
|
+
}
|
|
4780
|
+
continue;
|
|
4781
|
+
}
|
|
4782
|
+
const givenMatch = line.match(this.GIVEN_PATTERN);
|
|
4783
|
+
const whenMatch = line.match(this.WHEN_PATTERN);
|
|
4784
|
+
const thenMatch = line.match(this.THEN_PATTERN);
|
|
4785
|
+
const stateMatch = line.match(this.STATE_PATTERN);
|
|
4786
|
+
if (givenMatch && currentScenario) {
|
|
4787
|
+
currentScenario.given.push({
|
|
4788
|
+
description: givenMatch[1].trim(),
|
|
4789
|
+
type: this.detectConditionType(givenMatch[1]),
|
|
4790
|
+
validation: this.extractValidation(givenMatch[1])
|
|
4791
|
+
});
|
|
4792
|
+
section = "given";
|
|
4793
|
+
continue;
|
|
4794
|
+
}
|
|
4795
|
+
if (whenMatch && currentScenario) {
|
|
4796
|
+
const action = this.parseAction(whenMatch[1].trim());
|
|
4797
|
+
currentScenario.when.push(action);
|
|
4798
|
+
if (action.api) {
|
|
4799
|
+
currentScenario.relatedApis.push(action.api);
|
|
4800
|
+
}
|
|
4801
|
+
section = "when";
|
|
4802
|
+
continue;
|
|
4803
|
+
}
|
|
4804
|
+
if (thenMatch && currentScenario) {
|
|
4805
|
+
currentScenario.then.push({
|
|
4806
|
+
description: thenMatch[1].trim(),
|
|
4807
|
+
type: this.detectResultType(thenMatch[1]),
|
|
4808
|
+
expectedBehavior: this.extractExpectedBehavior(thenMatch[1])
|
|
4809
|
+
});
|
|
4810
|
+
section = "then";
|
|
4811
|
+
continue;
|
|
4812
|
+
}
|
|
4813
|
+
if (stateMatch && currentScenario) {
|
|
4814
|
+
const transition = this.parseStateTransition(stateMatch[1].trim());
|
|
4815
|
+
if (transition) {
|
|
4816
|
+
currentScenario.stateTransitions.push(transition);
|
|
4817
|
+
}
|
|
4818
|
+
section = "state";
|
|
4819
|
+
continue;
|
|
4820
|
+
}
|
|
4821
|
+
if (line && !line.startsWith("#") && !line.startsWith("---") && currentScenario) {
|
|
4822
|
+
const trimmed = line.replace(/^[\s-]*/, "");
|
|
4823
|
+
if (trimmed && section === "given" && currentScenario.given.length > 0) {
|
|
4824
|
+
currentScenario.given[currentScenario.given.length - 1].description += " " + trimmed;
|
|
4825
|
+
} else if (trimmed && section === "when" && currentScenario.when.length > 0) {
|
|
4826
|
+
currentScenario.when[currentScenario.when.length - 1].description += " " + trimmed;
|
|
4827
|
+
} else if (trimmed && section === "then" && currentScenario.then.length > 0) {
|
|
4828
|
+
currentScenario.then[currentScenario.then.length - 1].description += " " + trimmed;
|
|
4829
|
+
}
|
|
4830
|
+
}
|
|
4831
|
+
}
|
|
4832
|
+
if (currentScenario && currentScenario.title) {
|
|
4833
|
+
scenarios.push(this.finalizeScenario(currentScenario, scenarioOrder));
|
|
4834
|
+
}
|
|
4835
|
+
return scenarios;
|
|
4836
|
+
}
|
|
4837
|
+
/**
|
|
4838
|
+
* 从验收标准部分提取 BDD 场景
|
|
4839
|
+
*/
|
|
4840
|
+
static parseFromAcceptanceSection(acceptanceSection) {
|
|
4841
|
+
const bddSectionMatch = acceptanceSection.match(
|
|
4842
|
+
/(?:###\s*BDD[^\n]*|###\s*场景|###\s*行为驱动)[^]*(?=##|$)/i
|
|
4843
|
+
);
|
|
4844
|
+
if (bddSectionMatch) {
|
|
4845
|
+
return this.parseScenarios(bddSectionMatch[0]);
|
|
4846
|
+
}
|
|
4847
|
+
return this.convertFromLegacyCriteria(acceptanceSection);
|
|
4848
|
+
}
|
|
4849
|
+
/**
|
|
4850
|
+
* 从传统验收标准转换
|
|
4851
|
+
*/
|
|
4852
|
+
static convertFromLegacyCriteria(criteria) {
|
|
4853
|
+
const scenarios = [];
|
|
4854
|
+
const lines = criteria.split("\n");
|
|
4855
|
+
let scenarioId = 0;
|
|
4856
|
+
let currentScenario = null;
|
|
4857
|
+
for (const line of lines) {
|
|
4858
|
+
const criteriaMatch = line.match(/^-\s*\[\s*\]\s*(.+)$/);
|
|
4859
|
+
if (criteriaMatch) {
|
|
4860
|
+
if (!currentScenario) {
|
|
4861
|
+
scenarioId++;
|
|
4862
|
+
currentScenario = {
|
|
4863
|
+
id: `scenario-${scenarioId}`,
|
|
4864
|
+
title: `\u573A\u666F ${scenarioId}`,
|
|
4865
|
+
order: scenarioId,
|
|
4866
|
+
given: [],
|
|
4867
|
+
when: [],
|
|
4868
|
+
then: [],
|
|
4869
|
+
stateTransitions: []
|
|
4870
|
+
};
|
|
4871
|
+
}
|
|
4872
|
+
const text = criteriaMatch[1];
|
|
4873
|
+
const normalized = text.toLowerCase();
|
|
4874
|
+
if (normalized.includes("\u5DF2") || normalized.includes("\u6709") || normalized.includes("\u5B58\u5728")) {
|
|
4875
|
+
currentScenario.given.push({
|
|
4876
|
+
description: text,
|
|
4877
|
+
type: "data",
|
|
4878
|
+
validation: "\u68C0\u67E5\u6570\u636E\u5B58\u5728"
|
|
4879
|
+
});
|
|
4880
|
+
} else if (normalized.includes("\u5F53") || normalized.includes("\u63D0\u4EA4") || normalized.includes("\u70B9\u51FB")) {
|
|
4881
|
+
currentScenario.when.push({
|
|
4882
|
+
description: text,
|
|
4883
|
+
trigger: "user"
|
|
4884
|
+
});
|
|
4885
|
+
} else if (normalized.includes("\u5E94") || normalized.includes("\u8FD4\u56DE") || normalized.includes("\u663E\u793A")) {
|
|
4886
|
+
currentScenario.then.push({
|
|
4887
|
+
description: text,
|
|
4888
|
+
type: "response",
|
|
4889
|
+
expectedBehavior: text
|
|
4890
|
+
});
|
|
4891
|
+
}
|
|
4892
|
+
} else if (line.trim() === "" && currentScenario) {
|
|
4893
|
+
scenarios.push(this.finalizeScenario(currentScenario, scenarioId));
|
|
4894
|
+
currentScenario = null;
|
|
4895
|
+
}
|
|
4896
|
+
}
|
|
4897
|
+
if (currentScenario && currentScenario.title) {
|
|
4898
|
+
scenarios.push(this.finalizeScenario(currentScenario, scenarioId));
|
|
4899
|
+
}
|
|
4900
|
+
return scenarios;
|
|
4901
|
+
}
|
|
4902
|
+
/**
|
|
4903
|
+
* 检测条件类型
|
|
4904
|
+
*/
|
|
4905
|
+
static detectConditionType(description) {
|
|
4906
|
+
const lower = description.toLowerCase();
|
|
4907
|
+
if (lower.includes("\u6570\u636E") || lower.includes("\u8BB0\u5F55") || lower.includes("\u8868")) return "data";
|
|
4908
|
+
if (lower.includes("\u72B6\u6001") || lower.includes("\u5DF2") || lower.includes("\u672A")) return "state";
|
|
4909
|
+
return "user";
|
|
4910
|
+
}
|
|
4911
|
+
/**
|
|
4912
|
+
* 检测结果类型
|
|
4913
|
+
*/
|
|
4914
|
+
static detectResultType(description) {
|
|
4915
|
+
const lower = description.toLowerCase();
|
|
4916
|
+
if (lower.includes("\u8FD4\u56DE") || lower.includes("\u54CD\u5E94") || lower.includes("\u72B6\u6001\u7801")) return "response";
|
|
4917
|
+
if (lower.includes("\u72B6\u6001") || lower.includes("\u53D8\u6210") || lower.includes("\u66F4\u65B0")) return "state";
|
|
4918
|
+
if (lower.includes("\u663E\u793A") || lower.includes("\u9875\u9762") || lower.includes("\u754C\u9762")) return "ui";
|
|
4919
|
+
return "data";
|
|
4920
|
+
}
|
|
4921
|
+
/**
|
|
4922
|
+
* 提取验证方式
|
|
4923
|
+
*/
|
|
4924
|
+
static extractValidation(description) {
|
|
4925
|
+
const match = description.match(/\(([^)]+)\)/);
|
|
4926
|
+
return match ? match[1] : "\u68C0\u67E5\u6761\u4EF6\u6EE1\u8DB3";
|
|
4927
|
+
}
|
|
4928
|
+
/**
|
|
4929
|
+
* 提取预期行为
|
|
4930
|
+
*/
|
|
4931
|
+
static extractExpectedBehavior(description) {
|
|
4932
|
+
return description.replace(/^(应该|应|应当|应该会|会)/, "").trim();
|
|
4933
|
+
}
|
|
4934
|
+
/**
|
|
4935
|
+
* 解析动作
|
|
4936
|
+
*/
|
|
4937
|
+
static parseAction(description) {
|
|
4938
|
+
const apiMatch = description.match(/(?:API|POST|GET|PUT|DELETE|PATCH)\s*[::]?\s*`([^`]+)`/i);
|
|
4939
|
+
const uiMatch = description.match(/(?:点击|输入|选择|提交|拖拽)[::]?\s*(.+)/);
|
|
4940
|
+
return {
|
|
4941
|
+
description,
|
|
4942
|
+
api: apiMatch?.[1],
|
|
4943
|
+
uiAction: uiMatch?.[1],
|
|
4944
|
+
trigger: apiMatch ? "api" : "user"
|
|
4945
|
+
};
|
|
4946
|
+
}
|
|
4947
|
+
/**
|
|
4948
|
+
* 解析状态流转
|
|
4949
|
+
*/
|
|
4950
|
+
static parseStateTransition(description) {
|
|
4951
|
+
const arrowMatch = description.match(/`([^`]+)`\s*(?:→|->)\s*`([^`]+)`/);
|
|
4952
|
+
if (arrowMatch) {
|
|
4953
|
+
return {
|
|
4954
|
+
from: arrowMatch[1],
|
|
4955
|
+
trigger: description.split(/→|->/)[1]?.trim() || "\u5B8C\u6210",
|
|
4956
|
+
to: arrowMatch[2]
|
|
4957
|
+
};
|
|
4958
|
+
}
|
|
4959
|
+
const fromMatch = description.match(/(?:从|起始于|开始于)\s*`([^`]+)`/);
|
|
4960
|
+
const toMatch = description.match(/(?:到|变为|结束于)\s*`([^`]+)`/);
|
|
4961
|
+
const triggerMatch = description.match(/通过|当|使用\s*[::]?\s*(.+?)(?:,|$)/);
|
|
4962
|
+
if (fromMatch && toMatch) {
|
|
4963
|
+
return {
|
|
4964
|
+
from: fromMatch[1],
|
|
4965
|
+
trigger: triggerMatch?.[1] || "\u5B8C\u6210",
|
|
4966
|
+
to: toMatch[1]
|
|
4967
|
+
};
|
|
4968
|
+
}
|
|
4969
|
+
return null;
|
|
4970
|
+
}
|
|
4971
|
+
/**
|
|
4972
|
+
* 完成场景构建
|
|
4973
|
+
*/
|
|
4974
|
+
static finalizeScenario(scenario, order) {
|
|
4975
|
+
return {
|
|
4976
|
+
id: scenario.id || `scenario-${order}`,
|
|
4977
|
+
title: scenario.title || `\u573A\u666F ${order}`,
|
|
4978
|
+
order: scenario.order || order,
|
|
4979
|
+
asA: scenario.asA || "\u7528\u6237",
|
|
4980
|
+
iWant: scenario.iWant || "",
|
|
4981
|
+
soThat: scenario.soThat || "",
|
|
4982
|
+
given: scenario.given || [],
|
|
4983
|
+
when: scenario.when || [],
|
|
4984
|
+
then: scenario.then || [],
|
|
4985
|
+
stateTransitions: scenario.stateTransitions || [],
|
|
4986
|
+
relatedApis: scenario.relatedApis || [],
|
|
4987
|
+
relatedFeatures: scenario.relatedFeatures || []
|
|
4988
|
+
};
|
|
4989
|
+
}
|
|
4990
|
+
/**
|
|
4991
|
+
* 生成场景的 Markdown
|
|
4992
|
+
*/
|
|
4993
|
+
static generateMarkdown(scenario) {
|
|
4994
|
+
const lines = [];
|
|
4995
|
+
lines.push(`#### \u573A\u666F ${scenario.order}: ${scenario.title}`);
|
|
4996
|
+
lines.push("");
|
|
4997
|
+
if (scenario.asA) {
|
|
4998
|
+
lines.push(`**\u4F5C\u4E3A** ${scenario.asA}`);
|
|
4999
|
+
}
|
|
5000
|
+
if (scenario.iWant) {
|
|
5001
|
+
lines.push(`**\u6211\u5E0C\u671B** ${scenario.iWant}`);
|
|
5002
|
+
}
|
|
5003
|
+
if (scenario.soThat) {
|
|
5004
|
+
lines.push(`**\u4EE5\u4FBF** ${scenario.soThat}`);
|
|
5005
|
+
}
|
|
5006
|
+
lines.push("");
|
|
5007
|
+
for (const given of scenario.given) {
|
|
5008
|
+
lines.push(`- [GIVEN] ${given.description}`);
|
|
5009
|
+
}
|
|
5010
|
+
lines.push("");
|
|
5011
|
+
for (const when of scenario.when) {
|
|
5012
|
+
lines.push(`- [WHEN] ${when.description}`);
|
|
5013
|
+
}
|
|
5014
|
+
lines.push("");
|
|
5015
|
+
for (const then of scenario.then) {
|
|
5016
|
+
lines.push(`- [THEN] ${then.description}`);
|
|
5017
|
+
}
|
|
5018
|
+
lines.push("");
|
|
5019
|
+
if (scenario.stateTransitions.length > 0) {
|
|
5020
|
+
lines.push("**\u72B6\u6001\u6D41\u8F6C**:");
|
|
5021
|
+
for (const trans of scenario.stateTransitions) {
|
|
5022
|
+
lines.push(`- \`${trans.from}\` \u2192 \`${trans.to}\` (\u89E6\u53D1: ${trans.trigger})`);
|
|
5023
|
+
}
|
|
5024
|
+
lines.push("");
|
|
5025
|
+
}
|
|
5026
|
+
return lines.join("\n");
|
|
5027
|
+
}
|
|
5028
|
+
};
|
|
5029
|
+
}
|
|
5030
|
+
});
|
|
5031
|
+
|
|
4691
5032
|
// src/commands/accept.ts
|
|
4692
5033
|
import { Command as Command7 } from "commander";
|
|
4693
5034
|
import inquirer6 from "inquirer";
|
|
4694
5035
|
import path12 from "path";
|
|
5036
|
+
async function runTraditionalAcceptance(defaultSpec) {
|
|
5037
|
+
const selectedSpec = await selectSpec2(defaultSpec);
|
|
5038
|
+
const selectedMilestone = await selectMilestone2(selectedSpec);
|
|
5039
|
+
const result = await runAcceptanceCheck(selectedSpec, selectedMilestone);
|
|
5040
|
+
await generateAcceptanceReport(result);
|
|
5041
|
+
if (result.issues.length > 0) {
|
|
5042
|
+
await handleIssues(result);
|
|
5043
|
+
}
|
|
5044
|
+
await syncSpecStatus(selectedSpec, result);
|
|
5045
|
+
}
|
|
5046
|
+
async function runBDDAcceptance(defaultSpec, scenarioId) {
|
|
5047
|
+
const selectedSpec = await selectSpec2(defaultSpec);
|
|
5048
|
+
const specContent = await FileUtils.read(selectedSpec);
|
|
5049
|
+
const scenarios = BDDParser.parseFromAcceptanceSection(specContent);
|
|
5050
|
+
if (scenarios.length === 0) {
|
|
5051
|
+
logger.warn("\u672A\u627E\u5230 BDD \u573A\u666F\uFF0C\u4F7F\u7528\u4F20\u7EDF\u9A8C\u6536\u6A21\u5F0F");
|
|
5052
|
+
logger.info('\u8BF7\u5148\u8FD0\u884C "team-cli split-prd" \u751F\u6210\u5305\u542B BDD \u573A\u666F\u7684 spec');
|
|
5053
|
+
await runTraditionalAcceptance(selectedSpec);
|
|
5054
|
+
return;
|
|
5055
|
+
}
|
|
5056
|
+
const targetScenarios = scenarioId ? scenarios.filter((s) => s.id === scenarioId || s.title.toLowerCase().includes(scenarioId.toLowerCase())) : scenarios;
|
|
5057
|
+
if (targetScenarios.length === 0) {
|
|
5058
|
+
throw new Error(`\u672A\u627E\u5230\u573A\u666F: ${scenarioId}`);
|
|
5059
|
+
}
|
|
5060
|
+
logger.newLine();
|
|
5061
|
+
logger.info(`\u627E\u5230 ${scenarios.length} \u4E2A BDD \u573A\u666F\uFF0C\u5C06\u9A8C\u6536 ${targetScenarios.length} \u4E2A`);
|
|
5062
|
+
const results = [];
|
|
5063
|
+
for (const scenario of targetScenarios) {
|
|
5064
|
+
logger.newLine();
|
|
5065
|
+
logger.info(`\u9A8C\u6536\u573A\u666F: ${scenario.title}`);
|
|
5066
|
+
const result = await checkBDDScenario(scenario, selectedSpec);
|
|
5067
|
+
results.push(result);
|
|
5068
|
+
printScenarioResult(result);
|
|
5069
|
+
}
|
|
5070
|
+
await generateBDDReport(selectedSpec, results);
|
|
5071
|
+
await syncBDDStatus(selectedSpec, results);
|
|
5072
|
+
}
|
|
4695
5073
|
async function selectSpec2(defaultSpec) {
|
|
4696
5074
|
logger.step("\u6B65\u9AA4 1/4: \u9009\u62E9 spec \u6587\u4EF6...");
|
|
4697
5075
|
logger.newLine();
|
|
@@ -5238,6 +5616,377 @@ async function syncSpecStatus(specFile, result) {
|
|
|
5238
5616
|
}
|
|
5239
5617
|
}
|
|
5240
5618
|
}
|
|
5619
|
+
async function checkBDDScenario(scenario, specFile) {
|
|
5620
|
+
const result = {
|
|
5621
|
+
scenarioId: scenario.id,
|
|
5622
|
+
scenarioTitle: scenario.title,
|
|
5623
|
+
overallStatus: "pending",
|
|
5624
|
+
givenResults: [],
|
|
5625
|
+
whenResults: [],
|
|
5626
|
+
thenResults: [],
|
|
5627
|
+
stateTransitionResults: []
|
|
5628
|
+
};
|
|
5629
|
+
for (const given of scenario.given) {
|
|
5630
|
+
const checkResult = await verifyGivenCondition(given, specFile);
|
|
5631
|
+
result.givenResults.push(checkResult);
|
|
5632
|
+
}
|
|
5633
|
+
for (const when of scenario.when) {
|
|
5634
|
+
const checkResult = await verifyWhenAction(when, specFile);
|
|
5635
|
+
result.whenResults.push(checkResult);
|
|
5636
|
+
}
|
|
5637
|
+
for (const then of scenario.then) {
|
|
5638
|
+
const checkResult = await verifyThenResult(then, specFile);
|
|
5639
|
+
result.thenResults.push(checkResult);
|
|
5640
|
+
}
|
|
5641
|
+
for (const transition of scenario.stateTransitions) {
|
|
5642
|
+
const checkResult = await verifyStateTransition(transition, specFile);
|
|
5643
|
+
result.stateTransitionResults.push(checkResult);
|
|
5644
|
+
}
|
|
5645
|
+
const allResults = [
|
|
5646
|
+
...result.givenResults,
|
|
5647
|
+
...result.whenResults,
|
|
5648
|
+
...result.thenResults,
|
|
5649
|
+
...result.stateTransitionResults
|
|
5650
|
+
];
|
|
5651
|
+
if (allResults.every((r) => r.status === "pass")) {
|
|
5652
|
+
result.overallStatus = "pass";
|
|
5653
|
+
} else if (allResults.some((r) => r.status === "fail")) {
|
|
5654
|
+
result.overallStatus = "fail";
|
|
5655
|
+
} else {
|
|
5656
|
+
result.overallStatus = "pending";
|
|
5657
|
+
}
|
|
5658
|
+
return result;
|
|
5659
|
+
}
|
|
5660
|
+
async function verifyGivenCondition(given, specFile) {
|
|
5661
|
+
switch (given.type) {
|
|
5662
|
+
case "data":
|
|
5663
|
+
return verifyDataCondition(given);
|
|
5664
|
+
case "state":
|
|
5665
|
+
return verifyStateCondition(given);
|
|
5666
|
+
case "user":
|
|
5667
|
+
return verifyUserCondition(given);
|
|
5668
|
+
default:
|
|
5669
|
+
return { condition: given.description, status: "pending" };
|
|
5670
|
+
}
|
|
5671
|
+
}
|
|
5672
|
+
async function verifyDataCondition(given) {
|
|
5673
|
+
const tableMatch = given.description.match(/`?([a-z_]+)`?表/);
|
|
5674
|
+
const fieldMatch = given.description.match(/`?([a-z_]+)`?字段/);
|
|
5675
|
+
if (tableMatch) {
|
|
5676
|
+
const tableName = tableMatch[1];
|
|
5677
|
+
const tableFiles = await FileUtils.findFiles(`**/${tableName}.sql`, "database");
|
|
5678
|
+
const entityFiles = await FileUtils.findFiles(`**/${StringUtils.toPascalCase(tableName)}.java`, "backend/src");
|
|
5679
|
+
if (tableFiles.length > 0 || entityFiles.length > 0) {
|
|
5680
|
+
return {
|
|
5681
|
+
condition: given.description,
|
|
5682
|
+
status: "pass",
|
|
5683
|
+
evidence: tableFiles[0] || entityFiles[0]
|
|
5684
|
+
};
|
|
5685
|
+
}
|
|
5686
|
+
return {
|
|
5687
|
+
condition: given.description,
|
|
5688
|
+
status: "fail",
|
|
5689
|
+
evidence: `\u8868 ${tableName} \u672A\u627E\u5230`
|
|
5690
|
+
};
|
|
5691
|
+
}
|
|
5692
|
+
return { condition: given.description, status: "pending" };
|
|
5693
|
+
}
|
|
5694
|
+
async function verifyStateCondition(given) {
|
|
5695
|
+
const statusMatch = given.description.match(/`([^`]+)`状态/);
|
|
5696
|
+
const stateMatch = given.description.match(/`([^`]+)`/);
|
|
5697
|
+
const statusName = statusMatch?.[1] || stateMatch?.[1];
|
|
5698
|
+
if (statusName) {
|
|
5699
|
+
const enumFiles = [
|
|
5700
|
+
...await FileUtils.findFiles("**/*Status*.java", "backend/src"),
|
|
5701
|
+
...await FileUtils.findFiles("**/*Status*.ts", "frontend/src")
|
|
5702
|
+
];
|
|
5703
|
+
for (const ef of enumFiles) {
|
|
5704
|
+
const content = await FileUtils.read(ef);
|
|
5705
|
+
if (content.includes(statusName)) {
|
|
5706
|
+
return {
|
|
5707
|
+
condition: given.description,
|
|
5708
|
+
status: "pass",
|
|
5709
|
+
evidence: ef
|
|
5710
|
+
};
|
|
5711
|
+
}
|
|
5712
|
+
}
|
|
5713
|
+
return {
|
|
5714
|
+
condition: given.description,
|
|
5715
|
+
status: "fail",
|
|
5716
|
+
evidence: `\u72B6\u6001 ${statusName} \u672A\u627E\u5230`
|
|
5717
|
+
};
|
|
5718
|
+
}
|
|
5719
|
+
return { condition: given.description, status: "pending" };
|
|
5720
|
+
}
|
|
5721
|
+
async function verifyUserCondition(given) {
|
|
5722
|
+
const userMatch = given.description.match(/用户.*?`([^`]+)`/);
|
|
5723
|
+
if (userMatch) {
|
|
5724
|
+
return {
|
|
5725
|
+
condition: given.description,
|
|
5726
|
+
status: "pending",
|
|
5727
|
+
evidence: "\u9700\u8981\u4EBA\u5DE5\u9A8C\u8BC1\u7528\u6237\u72B6\u6001"
|
|
5728
|
+
};
|
|
5729
|
+
}
|
|
5730
|
+
return { condition: given.description, status: "pending" };
|
|
5731
|
+
}
|
|
5732
|
+
async function verifyWhenAction(when, specFile) {
|
|
5733
|
+
if (when.api) {
|
|
5734
|
+
const apiCheck = await verifyApiImplementationByPath(when.api);
|
|
5735
|
+
return {
|
|
5736
|
+
action: when.description,
|
|
5737
|
+
status: apiCheck.status,
|
|
5738
|
+
apiCalled: when.api,
|
|
5739
|
+
responseCode: apiCheck.responseCode
|
|
5740
|
+
};
|
|
5741
|
+
}
|
|
5742
|
+
if (when.uiAction) {
|
|
5743
|
+
const uiCheck = await verifyUIComponent(when.uiAction);
|
|
5744
|
+
return {
|
|
5745
|
+
action: when.description,
|
|
5746
|
+
status: uiCheck.status
|
|
5747
|
+
};
|
|
5748
|
+
}
|
|
5749
|
+
return { action: when.description, status: "pending" };
|
|
5750
|
+
}
|
|
5751
|
+
async function verifyApiImplementationByPath(apiPath) {
|
|
5752
|
+
const apiMatch = apiPath.match(/(GET|POST|PUT|DELETE|PATCH)\s+\/?(.+)/i);
|
|
5753
|
+
if (!apiMatch) {
|
|
5754
|
+
return { status: "pending" };
|
|
5755
|
+
}
|
|
5756
|
+
const method = apiMatch[1].toUpperCase();
|
|
5757
|
+
const path22 = "/" + apiMatch[2];
|
|
5758
|
+
const controllerFiles = await FileUtils.findFiles("**/*Controller.java", "backend/src");
|
|
5759
|
+
for (const cf of controllerFiles) {
|
|
5760
|
+
const content = await FileUtils.read(cf);
|
|
5761
|
+
if (content.includes(`"${path22}"`) || content.includes(`"${path22}/"`)) {
|
|
5762
|
+
const annotationMap = {
|
|
5763
|
+
GET: ["@GetMapping", "@RequestMapping(method = RequestMethod.GET)"],
|
|
5764
|
+
POST: ["@PostMapping", "@RequestMapping(method = RequestMethod.POST)"],
|
|
5765
|
+
PUT: ["@PutMapping", "@RequestMapping(method = RequestMethod.PUT)"],
|
|
5766
|
+
DELETE: ["@DeleteMapping", "@RequestMapping(method = RequestMethod.DELETE)"]
|
|
5767
|
+
};
|
|
5768
|
+
for (const annotation of annotationMap[method] || []) {
|
|
5769
|
+
if (content.includes(annotation)) {
|
|
5770
|
+
return { status: "pass", responseCode: 200 };
|
|
5771
|
+
}
|
|
5772
|
+
}
|
|
5773
|
+
}
|
|
5774
|
+
}
|
|
5775
|
+
return { status: "fail" };
|
|
5776
|
+
}
|
|
5777
|
+
async function verifyUIComponent(uiAction) {
|
|
5778
|
+
const componentFiles = await FileUtils.findFiles("**/*.tsx", "frontend/src");
|
|
5779
|
+
const viewFiles = await FileUtils.findFiles("**/*.vue", "frontend/src");
|
|
5780
|
+
const allFiles = [...componentFiles, ...viewFiles];
|
|
5781
|
+
for (const cf of allFiles) {
|
|
5782
|
+
const content = await FileUtils.read(cf);
|
|
5783
|
+
if (content.includes(uiAction) || content.toLowerCase().includes(uiAction.toLowerCase())) {
|
|
5784
|
+
return { status: "pass" };
|
|
5785
|
+
}
|
|
5786
|
+
}
|
|
5787
|
+
return { status: "pending" };
|
|
5788
|
+
}
|
|
5789
|
+
async function verifyThenResult(then, specFile) {
|
|
5790
|
+
switch (then.type) {
|
|
5791
|
+
case "response":
|
|
5792
|
+
return verifyResponseResult(then);
|
|
5793
|
+
case "state":
|
|
5794
|
+
return verifyStateResult(then);
|
|
5795
|
+
case "data":
|
|
5796
|
+
return verifyDataResult(then);
|
|
5797
|
+
case "ui":
|
|
5798
|
+
return verifyUIResult(then);
|
|
5799
|
+
default:
|
|
5800
|
+
return { result: then.description, status: "pending" };
|
|
5801
|
+
}
|
|
5802
|
+
}
|
|
5803
|
+
async function verifyResponseResult(then) {
|
|
5804
|
+
const codeMatch = then.description.match(/状态码\s*(\d+)/);
|
|
5805
|
+
const tokenMatch = then.description.match(/token/i);
|
|
5806
|
+
if (tokenMatch) {
|
|
5807
|
+
const tokenFiles = await FileUtils.findFiles("**/*Token*.java", "backend/src");
|
|
5808
|
+
if (tokenFiles.length > 0) {
|
|
5809
|
+
return { result: then.description, status: "pass", expectedValue: "JWT Token" };
|
|
5810
|
+
}
|
|
5811
|
+
return { result: then.description, status: "pending" };
|
|
5812
|
+
}
|
|
5813
|
+
if (codeMatch) {
|
|
5814
|
+
return { result: then.description, status: "pending", expectedValue: `HTTP ${codeMatch[1]}` };
|
|
5815
|
+
}
|
|
5816
|
+
return { result: then.description, status: "pending" };
|
|
5817
|
+
}
|
|
5818
|
+
async function verifyStateResult(then) {
|
|
5819
|
+
const stateMatch = then.description.match(/`([^`]+)`/);
|
|
5820
|
+
if (stateMatch) {
|
|
5821
|
+
const stateName = stateMatch[1];
|
|
5822
|
+
const enumFiles = [
|
|
5823
|
+
...await FileUtils.findFiles("**/*Status*.java", "backend/src"),
|
|
5824
|
+
...await FileUtils.findFiles("**/*Status*.ts", "frontend/src")
|
|
5825
|
+
];
|
|
5826
|
+
for (const ef of enumFiles) {
|
|
5827
|
+
const content = await FileUtils.read(ef);
|
|
5828
|
+
if (content.includes(stateName)) {
|
|
5829
|
+
return { result: then.description, status: "pass", expectedValue: stateName };
|
|
5830
|
+
}
|
|
5831
|
+
}
|
|
5832
|
+
return { result: then.description, status: "fail", expectedValue: stateName };
|
|
5833
|
+
}
|
|
5834
|
+
return { result: then.description, status: "pending" };
|
|
5835
|
+
}
|
|
5836
|
+
async function verifyDataResult(then) {
|
|
5837
|
+
return { result: then.description, status: "pending" };
|
|
5838
|
+
}
|
|
5839
|
+
async function verifyUIResult(then) {
|
|
5840
|
+
return { result: then.description, status: "pending" };
|
|
5841
|
+
}
|
|
5842
|
+
async function verifyStateTransition(transition, specFile) {
|
|
5843
|
+
const { from, trigger, to } = transition;
|
|
5844
|
+
const enumFiles = [
|
|
5845
|
+
...await FileUtils.findFiles("**/*Status*.java", "backend/src"),
|
|
5846
|
+
...await FileUtils.findFiles("**/*Status*.ts", "frontend/src"),
|
|
5847
|
+
...await FileUtils.findFiles("**/*State*.ts", "frontend/src")
|
|
5848
|
+
];
|
|
5849
|
+
const fromExists = await checkStateExists(from, enumFiles);
|
|
5850
|
+
const toExists = await checkStateExists(to, enumFiles);
|
|
5851
|
+
if (!fromExists || !toExists) {
|
|
5852
|
+
return {
|
|
5853
|
+
transition: `\`${from}\` \u2192 \`${to}\``,
|
|
5854
|
+
status: "fail",
|
|
5855
|
+
fromState: fromExists ? "\u2713" : "\u2717",
|
|
5856
|
+
toState: toExists ? "\u2713" : "\u2717"
|
|
5857
|
+
};
|
|
5858
|
+
}
|
|
5859
|
+
const serviceFiles = await FileUtils.findFiles("**/*Service*.java", "backend/src");
|
|
5860
|
+
for (const sf of serviceFiles) {
|
|
5861
|
+
const content = await FileUtils.read(sf);
|
|
5862
|
+
const methodPatterns = [
|
|
5863
|
+
new RegExp(`${from}\\s*\u2192\\s*${to}`, "i"),
|
|
5864
|
+
new RegExp(`from\\s*${from}\\s*to\\s*${to}`, "i")
|
|
5865
|
+
];
|
|
5866
|
+
for (const pattern of methodPatterns) {
|
|
5867
|
+
if (pattern.test(content)) {
|
|
5868
|
+
return {
|
|
5869
|
+
transition: `\`${from}\` \u2192 \`${to}\` (${trigger})`,
|
|
5870
|
+
status: "pass",
|
|
5871
|
+
fromState: from,
|
|
5872
|
+
toState: to
|
|
5873
|
+
};
|
|
5874
|
+
}
|
|
5875
|
+
}
|
|
5876
|
+
}
|
|
5877
|
+
return {
|
|
5878
|
+
transition: `\`${from}\` \u2192 \`${to}\` (${trigger})`,
|
|
5879
|
+
status: "pending",
|
|
5880
|
+
fromState: from,
|
|
5881
|
+
toState: to
|
|
5882
|
+
};
|
|
5883
|
+
}
|
|
5884
|
+
async function checkStateExists(state, files) {
|
|
5885
|
+
for (const file of files) {
|
|
5886
|
+
try {
|
|
5887
|
+
const content = await FileUtils.read(file);
|
|
5888
|
+
if (content.includes(state) || content.includes(`"${state}"`)) {
|
|
5889
|
+
return true;
|
|
5890
|
+
}
|
|
5891
|
+
} catch {
|
|
5892
|
+
continue;
|
|
5893
|
+
}
|
|
5894
|
+
}
|
|
5895
|
+
return false;
|
|
5896
|
+
}
|
|
5897
|
+
function printScenarioResult(result) {
|
|
5898
|
+
const icon = result.overallStatus === "pass" ? "\u2713" : result.overallStatus === "fail" ? "\u2717" : "\u25CB";
|
|
5899
|
+
console.log("");
|
|
5900
|
+
console.log(` ${icon} \u573A\u666F: ${result.scenarioTitle}`);
|
|
5901
|
+
const passGiven = result.givenResults.filter((r) => r.status === "pass").length;
|
|
5902
|
+
const passWhen = result.whenResults.filter((r) => r.status === "pass").length;
|
|
5903
|
+
const passThen = result.thenResults.filter((r) => r.status === "pass").length;
|
|
5904
|
+
const passTrans = result.stateTransitionResults.filter((r) => r.status === "pass").length;
|
|
5905
|
+
console.log(` Given: ${passGiven}/${result.givenResults.length}`);
|
|
5906
|
+
console.log(` When: ${passWhen}/${result.whenResults.length}`);
|
|
5907
|
+
console.log(` Then: ${passThen}/${result.thenResults.length}`);
|
|
5908
|
+
console.log(` State: ${passTrans}/${result.stateTransitionResults.length}`);
|
|
5909
|
+
}
|
|
5910
|
+
async function generateBDDReport(specFile, results) {
|
|
5911
|
+
const reportDir = "docs/bdd-reports";
|
|
5912
|
+
await FileUtils.ensureDir(reportDir);
|
|
5913
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
5914
|
+
const specName = path12.basename(specFile, ".md");
|
|
5915
|
+
const reportFile = path12.join(reportDir, `${timestamp}_${specName}_bdd.md`);
|
|
5916
|
+
const lines = [];
|
|
5917
|
+
lines.push("# BDD \u9A8C\u6536\u62A5\u544A");
|
|
5918
|
+
lines.push("");
|
|
5919
|
+
lines.push(`**Spec \u6587\u4EF6**: ${specFile}`);
|
|
5920
|
+
lines.push(`**\u9A8C\u6536\u65F6\u95F4**: ${(/* @__PURE__ */ new Date()).toLocaleString("zh-CN")}`);
|
|
5921
|
+
lines.push(`**\u573A\u666F\u6570\u91CF**: ${results.length}`);
|
|
5922
|
+
lines.push("");
|
|
5923
|
+
lines.push("## \u9A8C\u6536\u6C47\u603B");
|
|
5924
|
+
lines.push("");
|
|
5925
|
+
let totalPass = 0;
|
|
5926
|
+
let totalFail = 0;
|
|
5927
|
+
for (const r of results) {
|
|
5928
|
+
const statusIcon = r.overallStatus === "pass" ? "\u2713" : r.overallStatus === "fail" ? "\u2717" : "\u25CB";
|
|
5929
|
+
const givenIcon = r.givenResults.every((g) => g.status === "pass") ? "\u2713" : r.givenResults.some((g) => g.status === "fail") ? "\u2717" : "\u25CB";
|
|
5930
|
+
const whenIcon = r.whenResults.every((w) => w.status === "pass") ? "\u2713" : r.whenResults.some((w) => w.status === "fail") ? "\u2717" : "\u25CB";
|
|
5931
|
+
const thenIcon = r.thenResults.every((t) => t.status === "pass") ? "\u2713" : r.thenResults.some((t) => t.status === "fail") ? "\u2717" : "\u25CB";
|
|
5932
|
+
const transIcon = r.stateTransitionResults.every((t) => t.status === "pass") ? "\u2713" : r.stateTransitionResults.some((t) => t.status === "fail") ? "\u2717" : "\u25CB";
|
|
5933
|
+
lines.push(`- ${statusIcon} ${r.scenarioTitle} | Given:${givenIcon} When:${whenIcon} Then:${thenIcon} State:${transIcon}`);
|
|
5934
|
+
if (r.overallStatus === "pass") totalPass++;
|
|
5935
|
+
if (r.overallStatus === "fail") totalFail++;
|
|
5936
|
+
}
|
|
5937
|
+
lines.push("");
|
|
5938
|
+
lines.push(`**\u603B\u8BA1**: ${totalPass} \u4E2A\u901A\u8FC7, ${totalFail} \u4E2A\u5931\u8D25, ${results.length - totalPass - totalFail} \u4E2A\u5F85\u9A8C\u8BC1`);
|
|
5939
|
+
lines.push("");
|
|
5940
|
+
lines.push("## \u8BE6\u7EC6\u9A8C\u6536\u7ED3\u679C");
|
|
5941
|
+
lines.push("");
|
|
5942
|
+
for (const r of results) {
|
|
5943
|
+
lines.push(`### \u573A\u666F: ${r.scenarioTitle}`);
|
|
5944
|
+
lines.push("");
|
|
5945
|
+
lines.push("#### GIVEN (\u524D\u7F6E\u6761\u4EF6)");
|
|
5946
|
+
for (const g of r.givenResults) {
|
|
5947
|
+
const icon = g.status === "pass" ? "\u2713" : g.status === "fail" ? "\u2717" : "\u25CB";
|
|
5948
|
+
lines.push(`- ${icon} ${g.condition}`);
|
|
5949
|
+
if (g.evidence) {
|
|
5950
|
+
lines.push(` - \u8BC1\u636E: ${g.evidence}`);
|
|
5951
|
+
}
|
|
5952
|
+
}
|
|
5953
|
+
lines.push("");
|
|
5954
|
+
lines.push("#### WHEN (\u89E6\u53D1\u52A8\u4F5C)");
|
|
5955
|
+
for (const w of r.whenResults) {
|
|
5956
|
+
const icon = w.status === "pass" ? "\u2713" : w.status === "fail" ? "\u2717" : "\u25CB";
|
|
5957
|
+
lines.push(`- ${icon} ${w.action}`);
|
|
5958
|
+
if (w.apiCalled) {
|
|
5959
|
+
lines.push(` - API: ${w.apiCalled}`);
|
|
5960
|
+
}
|
|
5961
|
+
}
|
|
5962
|
+
lines.push("");
|
|
5963
|
+
lines.push("#### THEN (\u9884\u671F\u7ED3\u679C)");
|
|
5964
|
+
for (const t of r.thenResults) {
|
|
5965
|
+
const icon = t.status === "pass" ? "\u2713" : t.status === "fail" ? "\u2717" : "\u25CB";
|
|
5966
|
+
lines.push(`- ${icon} ${t.result}`);
|
|
5967
|
+
}
|
|
5968
|
+
lines.push("");
|
|
5969
|
+
lines.push("#### \u72B6\u6001\u6D41\u8F6C");
|
|
5970
|
+
for (const s of r.stateTransitionResults) {
|
|
5971
|
+
const icon = s.status === "pass" ? "\u2713" : s.status === "fail" ? "\u2717" : "\u25CB";
|
|
5972
|
+
lines.push(`- ${icon} ${s.transition}`);
|
|
5973
|
+
}
|
|
5974
|
+
lines.push("");
|
|
5975
|
+
lines.push("---");
|
|
5976
|
+
lines.push("");
|
|
5977
|
+
}
|
|
5978
|
+
await FileUtils.write(reportFile, lines.join("\n"));
|
|
5979
|
+
logger.success(`BDD \u9A8C\u6536\u62A5\u544A\u5DF2\u751F\u6210: ${reportFile}`);
|
|
5980
|
+
}
|
|
5981
|
+
async function syncBDDStatus(specFile, results) {
|
|
5982
|
+
const allPassed = results.every((r) => r.overallStatus === "pass");
|
|
5983
|
+
if (allPassed) {
|
|
5984
|
+
logger.success("\u6240\u6709 BDD \u573A\u666F\u9A8C\u6536\u901A\u8FC7");
|
|
5985
|
+
} else {
|
|
5986
|
+
const failedCount = results.filter((r) => r.overallStatus === "fail").length;
|
|
5987
|
+
logger.warn(`${failedCount} \u4E2A\u573A\u666F\u9A8C\u6536\u672A\u901A\u8FC7\uFF0C\u9700\u8981\u4FEE\u590D`);
|
|
5988
|
+
}
|
|
5989
|
+
}
|
|
5241
5990
|
var acceptCommand;
|
|
5242
5991
|
var init_accept = __esm({
|
|
5243
5992
|
"src/commands/accept.ts"() {
|
|
@@ -5245,7 +5994,8 @@ var init_accept = __esm({
|
|
|
5245
5994
|
init_esm_shims();
|
|
5246
5995
|
init_utils();
|
|
5247
5996
|
init_logger();
|
|
5248
|
-
|
|
5997
|
+
init_bdd_parser();
|
|
5998
|
+
acceptCommand = new Command7("accept").argument("[spec-file]", "Spec \u6587\u4EF6\u8DEF\u5F84").description("\u9A8C\u6536\u529F\u80FD - \u8D70\u67E5\u6240\u6709\u9700\u6C42\uFF0C\u9A8C\u8BC1\u8054\u8C03\u662F\u5426\u5B8C\u6210").option("--mode <mode>", "\u9A8C\u6536\u6A21\u5F0F: traditional (\u4F20\u7EDF) \u6216 bdd (\u884C\u4E3A\u9A71\u52A8)", "traditional").option("--scenario <id>", "\u6307\u5B9A\u9A8C\u6536\u7684 BDD \u573A\u666F ID").action(async (specFile, options) => {
|
|
5249
5999
|
try {
|
|
5250
6000
|
logger.header("\u9A8C\u6536\u6A21\u5F0F");
|
|
5251
6001
|
logger.newLine();
|
|
@@ -5256,14 +6006,11 @@ var init_accept = __esm({
|
|
|
5256
6006
|
process.exit(1);
|
|
5257
6007
|
}
|
|
5258
6008
|
logger.success("\u68C0\u6D4B\u5230\u9879\u76EE\u4E0A\u4E0B\u6587");
|
|
5259
|
-
|
|
5260
|
-
|
|
5261
|
-
|
|
5262
|
-
|
|
5263
|
-
|
|
5264
|
-
await handleIssues(result);
|
|
5265
|
-
}
|
|
5266
|
-
await syncSpecStatus(selectedSpec, result);
|
|
6009
|
+
if (options.mode === "bdd") {
|
|
6010
|
+
await runBDDAcceptance(specFile, options.scenario);
|
|
6011
|
+
} else {
|
|
6012
|
+
await runTraditionalAcceptance(specFile);
|
|
6013
|
+
}
|
|
5267
6014
|
logger.header("\u9A8C\u6536\u5B8C\u6210!");
|
|
5268
6015
|
logger.newLine();
|
|
5269
6016
|
} catch (error) {
|