@aigne/doc-smith 0.8.11-beta.3 → 0.8.11-beta.5
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/.aigne/doc-smith/config.yaml +1 -3
- package/.release-please-manifest.json +1 -1
- package/CHANGELOG.md +21 -0
- package/agents/clear/index.yaml +1 -0
- package/agents/evaluate/index.yaml +1 -0
- package/agents/generate/check-d2-diagram-valid.mjs +26 -0
- package/agents/generate/generate-d2-diagram.yaml +23 -0
- package/agents/generate/merge-d2-diagram.yaml +39 -0
- package/agents/init/index.mjs +42 -11
- package/agents/publish/index.yaml +1 -0
- package/agents/publish/publish-docs.mjs +17 -20
- package/agents/translate/index.yaml +1 -0
- package/agents/update/generate-document.yaml +25 -0
- package/agents/update/index.yaml +1 -0
- package/agents/update/update-single-document.yaml +1 -0
- package/agents/utils/choose-docs.mjs +22 -11
- package/package.json +2 -2
- package/prompts/detail/{d2-chart/rules.md → d2-diagram/rules-system.md} +41 -5
- package/prompts/detail/d2-diagram/rules-user.md +4 -0
- package/prompts/detail/document-rules.md +2 -3
- package/prompts/detail/generate-document.md +8 -2
- package/prompts/detail/update-document.md +1 -3
- package/prompts/translate/code-block.md +16 -0
- package/prompts/translate/translate-document.md +116 -20
- package/tests/agents/init/init.test.mjs +147 -19
- package/tests/agents/publish/publish-docs.test.mjs +99 -0
- package/tests/agents/utils/check-detail-result.test.mjs +2 -15
- package/tests/agents/utils/choose-docs.test.mjs +2 -9
- package/tests/utils/auth-utils.test.mjs +1 -1
- package/tests/utils/d2-utils.test.mjs +4 -4
- package/tests/utils/deploy.test.mjs +3 -10
- package/tests/utils/docs-finder-utils.test.mjs +12 -0
- package/tests/utils/kroki-utils.test.mjs +5 -5
- package/tests/utils/preferences-utils.test.mjs +5 -3
- package/tests/utils/save-value-to-config.test.mjs +3 -1
- package/utils/auth-utils.mjs +4 -0
- package/utils/constants/index.mjs +3 -0
- package/utils/d2-utils.mjs +11 -6
- package/utils/deploy.mjs +4 -20
- package/utils/docs-finder-utils.mjs +12 -1
- package/utils/kroki-utils.mjs +5 -4
- package/utils/markdown-checker.mjs +0 -20
- /package/prompts/detail/{d2-chart → d2-diagram}/official-examples.md +0 -0
|
@@ -1,26 +1,26 @@
|
|
|
1
1
|
<role_and_goal>
|
|
2
|
-
|
|
2
|
+
|
|
3
|
+
You are an **Elite Polyglot Localization and Translation Specialist** with extensive professional experience across multiple domains. Your core mission is to produce translations that are not only **100% accurate** to the source meaning but are also **natively fluent, highly readable, and culturally appropriate** in the target language.
|
|
4
|
+
|
|
5
|
+
Core Mandates:
|
|
6
|
+
|
|
7
|
+
1. Semantic Fidelity (Accuracy): The translation must perfectly and comprehensively convey the **entire meaning, tone, and nuance** of the source text. **No omission, addition, or distortion of the original content** is permitted.
|
|
8
|
+
2. Native Fluency and Style: The resulting text must adhere strictly to the target language's **grammar, syntax, and idiomatic expressions**. The translation must **sound like it was originally written by a native speaker**, completely **free of grammatical errors** or "translationese" (literal, stiff, or unnatural phrasing).
|
|
9
|
+
3. Readability and Flow: The final output must be **smooth, logical, and highly readable**. Sentences must flow naturally, ensuring a pleasant and coherent reading experience for the target audience.
|
|
10
|
+
4. Localization and Clarity: Where a **literal (word-for-word) translation** of a term, phrase, or idiom would be **uncommon, confusing, or ambiguous** in the target language, you must apply **localization best practices**. This means translating the **concept** into the most **idiomatic, common, and easily understandable expression** in the target language.
|
|
11
|
+
5. Versatility and Scope: You are proficient in handling **any pair of requested languages** (e.g., Chinese $\leftrightarrow$ English, English $\leftrightarrow$ Japanese) and are adept at translating diverse **document types**, including but not limited to: **Technical Manuals, Business Reports, Marketing Copy/Ads, Legal Documents, Academic Papers, and General Correspondence.**
|
|
3
12
|
</role_and_goal>
|
|
4
13
|
|
|
5
14
|
<translation_rules>
|
|
6
15
|
Translation Requirements:
|
|
7
16
|
|
|
8
|
-
-
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
12
|
-
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
- **Preserve Terms**: Retain specific terms in their original form, avoiding translation.
|
|
16
|
-
|
|
17
|
-
Translation Process:
|
|
18
|
-
|
|
19
|
-
- **Literal Translation**: Translate the original text word by word and sentence by sentence into the target language, ensuring every word's meaning is accurately conveyed.
|
|
20
|
-
- **Optimization**: Based on the literal translation, ensure the text stays faithful to the original meaning while making it more natural and aligned with **{{ language }}** usage.
|
|
21
|
-
- **Check for Omissions**: Compare the original with the literal translation to correct any meaning distortions or omissions.
|
|
22
|
-
- **Format Check**: Compare the original with the literal translation to ensure the translated content is complete. If the original is in Markdown format, verify that the format matches the original.
|
|
23
|
-
- **Final Output**: Output the optimized translation result, ensuring compliance with the above requirements (do not output the literal translation content).
|
|
17
|
+
- Avoid Exaggeration: Avoid using emotionally charged or subjective words (for example, "excited" or "shocked").
|
|
18
|
+
- Preserve Original Structure: Translate only the content portions without modifying tags or introducing any extra content or punctuation. Do not add markdown syntax at the outermost level. Ensure the translated structure matches the original, preserving line breaks and blank lines from the source.
|
|
19
|
+
- Strictly Protect Markdown Syntax: All Markdown syntax characters, including but not limited to `|` and `-` in tables, `*` and `-` in lists, `#` in headings, `` ` `` in code blocks, etc., must be **copied exactly**, with no modification, addition, deletion, or merging. Table separators (e.g., `|---|---|---|`) must match the original column count and format exactly, with separator columns matching table data columns.
|
|
20
|
+
- Use Terminology Reference: Ensure accuracy and consistency of professional terminology.
|
|
21
|
+
- Preserve Terms: Retain specific terms in their original form, avoiding translation.
|
|
22
|
+
|
|
23
|
+
{% include "./code-block.md" %}
|
|
24
24
|
</translation_rules>
|
|
25
25
|
|
|
26
26
|
|
|
@@ -57,6 +57,9 @@ Terms to preserve (do not translate):
|
|
|
57
57
|
</terms>
|
|
58
58
|
|
|
59
59
|
<example>
|
|
60
|
+
<example_item>
|
|
61
|
+
**Special Note**: Keep table separators `|---|---|---|` unchanged from the original
|
|
62
|
+
|
|
60
63
|
<before_translate>
|
|
61
64
|
| Name | Type | Description |
|
|
62
65
|
|---|---|---|
|
|
@@ -71,9 +74,10 @@ Terms to preserve (do not translate):
|
|
|
71
74
|
| `id` | `string` | 要更新的 Webhook 端点的唯一标识符。 |
|
|
72
75
|
| `data` | `PartialDeep<ABTNodeClient.WebhookEndpointStateInput>` | 包含要更新的 Webhook 端点字段的对象。 |
|
|
73
76
|
</after_translate>
|
|
77
|
+
</example_item>
|
|
74
78
|
|
|
75
|
-
|
|
76
|
-
|
|
79
|
+
<example_item>
|
|
80
|
+
**Special Note**: All x-field component attributes must maintain the original format. Only translate the description content within data-desc attributes or x-field-desc elements
|
|
77
81
|
<before_translate>
|
|
78
82
|
|
|
79
83
|
<x-field data-name="teamDid" data-type="string" data-required="true" data-desc="The DID of the team or Blocklet managing the webhook."></x-field>
|
|
@@ -89,8 +93,100 @@ Terms to preserve (do not translate):
|
|
|
89
93
|
<x-field-desc markdown>您的 **API 密钥**,用于身份验证。请从 `设置 > API 密钥` 部分生成一个。</x-field-desc>
|
|
90
94
|
</x-field>
|
|
91
95
|
</after_translate>
|
|
96
|
+
</example_item>
|
|
97
|
+
|
|
98
|
+
<example_item>
|
|
99
|
+
**Special Note**: In code blocks, only translate comments while keeping all other code content (variables, functions, syntax) unchanged
|
|
100
|
+
|
|
101
|
+
<before_translate>
|
|
102
|
+
```xxx
|
|
103
|
+
// Initialize the API client
|
|
104
|
+
const client = new APIClient({
|
|
105
|
+
apiKey: 'your-api-key', // Replace with your actual API key
|
|
106
|
+
baseUrl: 'https://api.example.com'
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
const errorMessage = 'Failed to fetch user data';
|
|
110
|
+
const successMessage = 'User data retrieved successfully';
|
|
111
|
+
|
|
112
|
+
// Send request to get user data
|
|
113
|
+
async function getUserData(userId) {
|
|
114
|
+
console.log('Starting user data fetch for ID:', userId);
|
|
115
|
+
|
|
116
|
+
try {
|
|
117
|
+
// Fetch user information from the API
|
|
118
|
+
const result = await client.get(`/users/${userId}`);
|
|
119
|
+
console.log('API response received');
|
|
120
|
+
console.log(successMessage);
|
|
121
|
+
return result;
|
|
122
|
+
} catch (error) {
|
|
123
|
+
console.error('Error occurred:', error.message);
|
|
124
|
+
throw new Error(errorMessage);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
```
|
|
128
|
+
</before_translate>
|
|
129
|
+
|
|
130
|
+
<after_translate>
|
|
131
|
+
```xxx
|
|
132
|
+
// 初始化 API 客户端
|
|
133
|
+
const client = new APIClient({
|
|
134
|
+
apiKey: 'your-api-key', // 替换为您的实际 API 密钥
|
|
135
|
+
baseUrl: 'https://api.example.com'
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
const errorMessage = 'Failed to fetch user data';
|
|
139
|
+
const successMessage = 'User data retrieved successfully';
|
|
140
|
+
|
|
141
|
+
// 发送请求获取用户数据
|
|
142
|
+
async function getUserData(userId) {
|
|
143
|
+
console.log('Starting user data fetch for ID:', userId);
|
|
144
|
+
|
|
145
|
+
try {
|
|
146
|
+
// 从 API 获取用户信息
|
|
147
|
+
const result = await client.get(`/users/${userId}`);
|
|
148
|
+
console.log('API response received');
|
|
149
|
+
console.log(successMessage);
|
|
150
|
+
return result;
|
|
151
|
+
} catch (error) {
|
|
152
|
+
console.error('Error occurred:', error.message);
|
|
153
|
+
throw new Error(errorMessage);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
```
|
|
157
|
+
</after_translate>
|
|
158
|
+
</example_item>
|
|
159
|
+
|
|
160
|
+
<example_item>
|
|
161
|
+
**Special Note**: **Command execution and log printing** should untranslated
|
|
162
|
+
|
|
163
|
+
<before_translate>
|
|
164
|
+
```text Timeout Error Message
|
|
165
|
+
Blocklet Server failed to stop within 5 minutes
|
|
166
|
+
You can stop blocklet server with blocklet stop --force
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
```bash Success Output
|
|
170
|
+
$ cli log
|
|
171
|
+
|
|
172
|
+
Cache for server cleared: [list of cleared cache keys]
|
|
173
|
+
```
|
|
174
|
+
</before_translate>
|
|
175
|
+
|
|
176
|
+
<after_translate>
|
|
177
|
+
```text 超时错误消息
|
|
178
|
+
Blocklet Server failed to stop within 5 minutes
|
|
179
|
+
You can stop blocklet server with blocklet stop --force
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
```bash 成功输出
|
|
183
|
+
$ cli log
|
|
184
|
+
|
|
185
|
+
Cache for server cleared: [list of cleared cache keys]
|
|
186
|
+
```
|
|
187
|
+
</after_translate>
|
|
188
|
+
</example_item>
|
|
92
189
|
|
|
93
|
-
**Special Note**: All x-field component attributes must maintain the original format. Only translate the description content within data-desc attributes or x-field-desc elements
|
|
94
190
|
</example>
|
|
95
191
|
|
|
96
192
|
Original text as follows:
|
|
@@ -986,11 +986,12 @@ describe("init", () => {
|
|
|
986
986
|
const mockResponses = {
|
|
987
987
|
checkbox_1: ["getStarted", "findAnswers"], // Document purpose
|
|
988
988
|
checkbox_2: ["developers"], // Target audience
|
|
989
|
-
|
|
990
|
-
select_4: "
|
|
991
|
-
select_5: "
|
|
992
|
-
|
|
993
|
-
|
|
989
|
+
input_3: "Custom rules for documentation", // Custom rules
|
|
990
|
+
select_4: "domainFamiliar", // Reader knowledge level
|
|
991
|
+
select_5: "balancedCoverage", // Documentation depth
|
|
992
|
+
select_6: "en", // Primary language
|
|
993
|
+
checkbox_7: ["zh", "ja"], // Translation languages
|
|
994
|
+
input_8: join(tempDir, "docs"), // Documentation directory
|
|
994
995
|
search: "", // Source paths (empty to finish)
|
|
995
996
|
};
|
|
996
997
|
|
|
@@ -1023,6 +1024,7 @@ describe("init", () => {
|
|
|
1023
1024
|
|
|
1024
1025
|
expect(config.documentPurpose).toEqual(["getStarted", "findAnswers"]);
|
|
1025
1026
|
expect(config.targetAudienceTypes).toEqual(["developers"]);
|
|
1027
|
+
expect(config.rules).toBe("Custom rules for documentation");
|
|
1026
1028
|
expect(config.readerKnowledgeLevel).toBe("domainFamiliar");
|
|
1027
1029
|
expect(config.documentationDepth).toBe("balancedCoverage");
|
|
1028
1030
|
expect(config.locale).toBe("en");
|
|
@@ -1042,11 +1044,12 @@ describe("init", () => {
|
|
|
1042
1044
|
checkbox_1: ["mixedPurpose"], // Document purpose - triggers follow-up
|
|
1043
1045
|
checkbox: ["completeTasks", "findAnswers"], // Top priorities after mixedPurpose
|
|
1044
1046
|
checkbox_2: ["developers", "devops"], // Target audience
|
|
1045
|
-
|
|
1046
|
-
select_4: "
|
|
1047
|
-
select_5: "
|
|
1048
|
-
|
|
1049
|
-
|
|
1047
|
+
input_3: "Custom rules for documentation", // Custom rules
|
|
1048
|
+
select_4: "experiencedUsers", // Reader knowledge level
|
|
1049
|
+
select_5: "comprehensive", // Documentation depth
|
|
1050
|
+
select_6: "zh-CN", // Primary language
|
|
1051
|
+
checkbox_7: ["en"], // Translation languages
|
|
1052
|
+
input_8: join(tempDir, "documentation"), // Documentation directory
|
|
1050
1053
|
search: "", // Source paths (empty to finish)
|
|
1051
1054
|
};
|
|
1052
1055
|
|
|
@@ -1071,6 +1074,7 @@ describe("init", () => {
|
|
|
1071
1074
|
|
|
1072
1075
|
expect(config.documentPurpose).toEqual(["completeTasks", "findAnswers"]);
|
|
1073
1076
|
expect(config.targetAudienceTypes).toEqual(["developers", "devops"]);
|
|
1077
|
+
expect(config.rules).toBe("Custom rules for documentation");
|
|
1074
1078
|
expect(config.readerKnowledgeLevel).toBe("experiencedUsers");
|
|
1075
1079
|
expect(config.documentationDepth).toBe("comprehensive");
|
|
1076
1080
|
expect(config.locale).toBe("zh-CN");
|
|
@@ -1087,11 +1091,12 @@ describe("init", () => {
|
|
|
1087
1091
|
const mockResponses = {
|
|
1088
1092
|
checkbox_1: ["getStarted"], // Document purpose
|
|
1089
1093
|
checkbox_2: ["endUsers"], // Target audience
|
|
1090
|
-
|
|
1091
|
-
select_4: "
|
|
1092
|
-
select_5: "
|
|
1093
|
-
|
|
1094
|
-
|
|
1094
|
+
input_3: "Custom rules for documentation", // Custom rules
|
|
1095
|
+
select_4: "completeBeginners", // Reader knowledge level
|
|
1096
|
+
select_5: "essentialOnly", // Documentation depth
|
|
1097
|
+
select_6: "en", // Primary language
|
|
1098
|
+
checkbox_7: [], // No translation languages
|
|
1099
|
+
input_8: join(tempDir, "simple-docs"), // Documentation directory
|
|
1095
1100
|
search: "", // Source paths (empty to finish)
|
|
1096
1101
|
};
|
|
1097
1102
|
|
|
@@ -1115,6 +1120,7 @@ describe("init", () => {
|
|
|
1115
1120
|
|
|
1116
1121
|
expect(config.documentPurpose).toEqual(["getStarted"]);
|
|
1117
1122
|
expect(config.targetAudienceTypes).toEqual(["endUsers"]);
|
|
1123
|
+
expect(config.rules).toBe("Custom rules for documentation");
|
|
1118
1124
|
expect(config.readerKnowledgeLevel).toBe("completeBeginners");
|
|
1119
1125
|
expect(config.documentationDepth).toBe("essentialOnly");
|
|
1120
1126
|
expect(config.locale).toBe("en");
|
|
@@ -1298,7 +1304,7 @@ describe("init", () => {
|
|
|
1298
1304
|
let validateCalled = false;
|
|
1299
1305
|
const mockPrompts = {
|
|
1300
1306
|
checkbox: (options) => {
|
|
1301
|
-
if (options.message.includes("[1/
|
|
1307
|
+
if (options.message.includes("[1/9]") && options.validate) {
|
|
1302
1308
|
// Test the validation function directly
|
|
1303
1309
|
const validationResult = options.validate([]);
|
|
1304
1310
|
expect(validationResult).toBe(
|
|
@@ -1336,10 +1342,10 @@ describe("init", () => {
|
|
|
1336
1342
|
let audienceValidateCalled = false;
|
|
1337
1343
|
const mockPrompts = {
|
|
1338
1344
|
checkbox: (options) => {
|
|
1339
|
-
if (options.message.includes("[1/
|
|
1345
|
+
if (options.message.includes("[1/9]")) {
|
|
1340
1346
|
return Promise.resolve(["getStarted"]); // Valid document purpose
|
|
1341
1347
|
}
|
|
1342
|
-
if (options.message.includes("[2/
|
|
1348
|
+
if (options.message.includes("[2/9]") && options.validate) {
|
|
1343
1349
|
// Test the validation function for target audience
|
|
1344
1350
|
const validationResult = options.validate([]);
|
|
1345
1351
|
expect(validationResult).toBe("Please choose at least one audience.");
|
|
@@ -1374,7 +1380,7 @@ describe("init", () => {
|
|
|
1374
1380
|
let priorityValidateCalled = false;
|
|
1375
1381
|
const mockPrompts = {
|
|
1376
1382
|
checkbox: (options) => {
|
|
1377
|
-
if (options.message.includes("[1/
|
|
1383
|
+
if (options.message.includes("[1/9]")) {
|
|
1378
1384
|
return Promise.resolve(["mixedPurpose"]); // Trigger follow-up question
|
|
1379
1385
|
}
|
|
1380
1386
|
// This is the follow-up priority selection
|
|
@@ -1438,6 +1444,128 @@ describe("init", () => {
|
|
|
1438
1444
|
});
|
|
1439
1445
|
});
|
|
1440
1446
|
|
|
1447
|
+
describe("checkOnly parameter", () => {
|
|
1448
|
+
test("should exit silently when checkOnly is true and config exists", async () => {
|
|
1449
|
+
const tempDir = await createTempDir();
|
|
1450
|
+
|
|
1451
|
+
try {
|
|
1452
|
+
// Create existing config file
|
|
1453
|
+
const configPath = join(tempDir, "config.yaml");
|
|
1454
|
+
const existingConfig = `projectName: "Test Project"
|
|
1455
|
+
locale: "en"
|
|
1456
|
+
documentPurpose:
|
|
1457
|
+
- getStarted
|
|
1458
|
+
targetAudienceTypes:
|
|
1459
|
+
- developers`;
|
|
1460
|
+
await fs.writeFile(configPath, existingConfig, "utf8");
|
|
1461
|
+
|
|
1462
|
+
// The function should continue normally when config exists
|
|
1463
|
+
// No mocking needed as it doesn't log or exit in this case
|
|
1464
|
+
const result = await init(
|
|
1465
|
+
{
|
|
1466
|
+
outputPath: tempDir,
|
|
1467
|
+
fileName: "config.yaml",
|
|
1468
|
+
checkOnly: true,
|
|
1469
|
+
},
|
|
1470
|
+
{ prompts: {} }, // Options not needed for checkOnly
|
|
1471
|
+
);
|
|
1472
|
+
|
|
1473
|
+
// Should return loaded config
|
|
1474
|
+
expect(result).toBeDefined();
|
|
1475
|
+
expect(result.projectName).toBe("Test Project");
|
|
1476
|
+
expect(result.locale).toBe("en");
|
|
1477
|
+
} finally {
|
|
1478
|
+
await cleanupTempDir(tempDir);
|
|
1479
|
+
}
|
|
1480
|
+
});
|
|
1481
|
+
|
|
1482
|
+
test("should show reminder and exit when checkOnly is true and config doesn't exist", async () => {
|
|
1483
|
+
const tempDir = await createTempDir();
|
|
1484
|
+
|
|
1485
|
+
try {
|
|
1486
|
+
// Mock console.log and process.exit
|
|
1487
|
+
const originalLog = console.log;
|
|
1488
|
+
const originalExit = process.exit;
|
|
1489
|
+
const logMessages = [];
|
|
1490
|
+
let exitCalled = false;
|
|
1491
|
+
let exitCode = null;
|
|
1492
|
+
|
|
1493
|
+
console.log = (...args) => logMessages.push(args.join(" "));
|
|
1494
|
+
process.exit = (code) => {
|
|
1495
|
+
exitCalled = true;
|
|
1496
|
+
exitCode = code;
|
|
1497
|
+
throw new Error("process.exit called"); // Prevent actual exit
|
|
1498
|
+
};
|
|
1499
|
+
|
|
1500
|
+
try {
|
|
1501
|
+
await expect(
|
|
1502
|
+
init(
|
|
1503
|
+
{
|
|
1504
|
+
outputPath: tempDir,
|
|
1505
|
+
fileName: "config.yaml",
|
|
1506
|
+
checkOnly: true,
|
|
1507
|
+
},
|
|
1508
|
+
{ prompts: {} },
|
|
1509
|
+
),
|
|
1510
|
+
).rejects.toThrow("process.exit called");
|
|
1511
|
+
|
|
1512
|
+
// Should log reminder messages
|
|
1513
|
+
expect(logMessages.some((msg) => msg.includes("No configuration found"))).toBe(true);
|
|
1514
|
+
expect(logMessages.some((msg) => msg.includes("aigne doc init"))).toBe(true);
|
|
1515
|
+
|
|
1516
|
+
// Should call process.exit(0)
|
|
1517
|
+
expect(exitCalled).toBe(true);
|
|
1518
|
+
expect(exitCode).toBe(0);
|
|
1519
|
+
} finally {
|
|
1520
|
+
console.log = originalLog;
|
|
1521
|
+
process.exit = originalExit;
|
|
1522
|
+
}
|
|
1523
|
+
} finally {
|
|
1524
|
+
await cleanupTempDir(tempDir);
|
|
1525
|
+
}
|
|
1526
|
+
});
|
|
1527
|
+
|
|
1528
|
+
test("should not interfere with normal workflow when checkOnly is false", async () => {
|
|
1529
|
+
const tempDir = await createTempDir();
|
|
1530
|
+
|
|
1531
|
+
try {
|
|
1532
|
+
const mockPrompts = createMockPrompts({
|
|
1533
|
+
checkbox_1: ["getStarted"],
|
|
1534
|
+
checkbox_2: ["developers"],
|
|
1535
|
+
select_3: "domainFamiliar",
|
|
1536
|
+
select_4: "balancedCoverage",
|
|
1537
|
+
select_5: "en",
|
|
1538
|
+
checkbox_6: [],
|
|
1539
|
+
input_7: join(tempDir, "docs"),
|
|
1540
|
+
search: "",
|
|
1541
|
+
});
|
|
1542
|
+
|
|
1543
|
+
const options = { prompts: mockPrompts };
|
|
1544
|
+
|
|
1545
|
+
const result = await init(
|
|
1546
|
+
{
|
|
1547
|
+
outputPath: tempDir,
|
|
1548
|
+
fileName: "config.yaml",
|
|
1549
|
+
checkOnly: false, // Explicit false
|
|
1550
|
+
},
|
|
1551
|
+
options,
|
|
1552
|
+
);
|
|
1553
|
+
|
|
1554
|
+
expect(result).toBeDefined();
|
|
1555
|
+
|
|
1556
|
+
// Check that config file was created normally
|
|
1557
|
+
const configPath = join(tempDir, "config.yaml");
|
|
1558
|
+
const configExists = await fs
|
|
1559
|
+
.access(configPath)
|
|
1560
|
+
.then(() => true)
|
|
1561
|
+
.catch(() => false);
|
|
1562
|
+
expect(configExists).toBe(true);
|
|
1563
|
+
} finally {
|
|
1564
|
+
await cleanupTempDir(tempDir);
|
|
1565
|
+
}
|
|
1566
|
+
});
|
|
1567
|
+
});
|
|
1568
|
+
|
|
1441
1569
|
describe("Advanced source path scenarios", () => {
|
|
1442
1570
|
test("should handle source path validation and duplicate detection", async () => {
|
|
1443
1571
|
const tempDir = await createTempDir();
|
|
@@ -15,6 +15,7 @@ import publishDocs from "../../../agents/publish/publish-docs.mjs";
|
|
|
15
15
|
import * as authUtils from "../../../utils/auth-utils.mjs";
|
|
16
16
|
import * as d2Utils from "../../../utils/d2-utils.mjs";
|
|
17
17
|
import * as utils from "../../../utils/utils.mjs";
|
|
18
|
+
import * as deployUtils from "../../../utils/deploy.mjs";
|
|
18
19
|
|
|
19
20
|
// Mock all external dependencies
|
|
20
21
|
const mockPublishDocs = {
|
|
@@ -51,6 +52,7 @@ describe("publish-docs", () => {
|
|
|
51
52
|
let getGithubRepoUrlSpy;
|
|
52
53
|
let loadConfigFromFileSpy;
|
|
53
54
|
let saveValueToConfigSpy;
|
|
55
|
+
let deploySpy;
|
|
54
56
|
|
|
55
57
|
beforeAll(() => {
|
|
56
58
|
// Apply mocks for external dependencies only
|
|
@@ -98,6 +100,10 @@ describe("publish-docs", () => {
|
|
|
98
100
|
);
|
|
99
101
|
loadConfigFromFileSpy = spyOn(utils, "loadConfigFromFile").mockResolvedValue({});
|
|
100
102
|
saveValueToConfigSpy = spyOn(utils, "saveValueToConfig").mockResolvedValue();
|
|
103
|
+
deploySpy = spyOn(deployUtils, "deploy").mockResolvedValue({
|
|
104
|
+
appUrl: "https://deployed.example.com",
|
|
105
|
+
token: "deploy-token",
|
|
106
|
+
});
|
|
101
107
|
|
|
102
108
|
mockOptions = {
|
|
103
109
|
prompts: {
|
|
@@ -125,6 +131,7 @@ describe("publish-docs", () => {
|
|
|
125
131
|
getGithubRepoUrlSpy?.mockRestore();
|
|
126
132
|
loadConfigFromFileSpy?.mockRestore();
|
|
127
133
|
saveValueToConfigSpy?.mockRestore();
|
|
134
|
+
deploySpy?.mockRestore();
|
|
128
135
|
});
|
|
129
136
|
|
|
130
137
|
// BASIC FUNCTIONALITY TESTS
|
|
@@ -593,6 +600,98 @@ describe("publish-docs", () => {
|
|
|
593
600
|
expect(mockOptions.prompts.select).not.toHaveBeenCalled();
|
|
594
601
|
});
|
|
595
602
|
|
|
603
|
+
// RESUME PREVIOUS WEBSITE SETUP TESTS
|
|
604
|
+
test("should show resume option when checkoutId exists in config", async () => {
|
|
605
|
+
loadConfigFromFileSpy.mockResolvedValue({
|
|
606
|
+
checkoutId: "cached-checkout-123",
|
|
607
|
+
});
|
|
608
|
+
mockOptions.prompts.select.mockResolvedValue("default");
|
|
609
|
+
|
|
610
|
+
await publishDocs(
|
|
611
|
+
{
|
|
612
|
+
docsDir: "./docs",
|
|
613
|
+
appUrl: "https://docsmith.aigne.io",
|
|
614
|
+
},
|
|
615
|
+
mockOptions,
|
|
616
|
+
);
|
|
617
|
+
|
|
618
|
+
expect(mockOptions.prompts.select).toHaveBeenCalledWith(
|
|
619
|
+
expect.objectContaining({
|
|
620
|
+
message: "Select platform to publish your documents:",
|
|
621
|
+
choices: expect.arrayContaining([
|
|
622
|
+
expect.objectContaining({
|
|
623
|
+
name: expect.stringContaining("Resume previous website setup"),
|
|
624
|
+
value: "new-instance-continue",
|
|
625
|
+
}),
|
|
626
|
+
]),
|
|
627
|
+
}),
|
|
628
|
+
);
|
|
629
|
+
|
|
630
|
+
// Verify the exact text content
|
|
631
|
+
const selectCall = mockOptions.prompts.select.mock.calls[0][0];
|
|
632
|
+
const resumeChoice = selectCall.choices.find(
|
|
633
|
+
(choice) => choice.value === "new-instance-continue",
|
|
634
|
+
);
|
|
635
|
+
expect(resumeChoice.name).toContain("Resume previous website setup");
|
|
636
|
+
expect(resumeChoice.name).toContain("Already paid.");
|
|
637
|
+
expect(resumeChoice.name).toContain(
|
|
638
|
+
"Continue where you left off. Your payment has already been processed.",
|
|
639
|
+
);
|
|
640
|
+
});
|
|
641
|
+
|
|
642
|
+
test("should not show resume option when no checkoutId in config", async () => {
|
|
643
|
+
loadConfigFromFileSpy.mockResolvedValue({});
|
|
644
|
+
mockOptions.prompts.select.mockResolvedValue("default");
|
|
645
|
+
|
|
646
|
+
await publishDocs(
|
|
647
|
+
{
|
|
648
|
+
docsDir: "./docs",
|
|
649
|
+
appUrl: "https://docsmith.aigne.io",
|
|
650
|
+
},
|
|
651
|
+
mockOptions,
|
|
652
|
+
);
|
|
653
|
+
|
|
654
|
+
expect(mockOptions.prompts.select).toHaveBeenCalledWith(
|
|
655
|
+
expect.objectContaining({
|
|
656
|
+
message: "Select platform to publish your documents:",
|
|
657
|
+
choices: expect.not.arrayContaining([
|
|
658
|
+
expect.objectContaining({
|
|
659
|
+
value: "new-instance-continue",
|
|
660
|
+
}),
|
|
661
|
+
]),
|
|
662
|
+
}),
|
|
663
|
+
);
|
|
664
|
+
});
|
|
665
|
+
|
|
666
|
+
test("should handle resume previous website setup selection", async () => {
|
|
667
|
+
deploySpy.mockResolvedValue({
|
|
668
|
+
appUrl: "https://resumed.example.com",
|
|
669
|
+
token: "resume-token",
|
|
670
|
+
});
|
|
671
|
+
|
|
672
|
+
loadConfigFromFileSpy.mockResolvedValue({
|
|
673
|
+
checkoutId: "cached-checkout-123",
|
|
674
|
+
paymentUrl: "https://payment.example.com",
|
|
675
|
+
});
|
|
676
|
+
mockOptions.prompts.select.mockResolvedValue("new-instance-continue");
|
|
677
|
+
|
|
678
|
+
const consoleSpy = spyOn(console, "log").mockImplementation(() => {});
|
|
679
|
+
|
|
680
|
+
await publishDocs(
|
|
681
|
+
{
|
|
682
|
+
docsDir: "./docs",
|
|
683
|
+
appUrl: "https://docsmith.aigne.io",
|
|
684
|
+
},
|
|
685
|
+
mockOptions,
|
|
686
|
+
);
|
|
687
|
+
|
|
688
|
+
expect(consoleSpy).toHaveBeenCalledWith("\nResuming your previous website setup...");
|
|
689
|
+
expect(deploySpy).toHaveBeenCalledWith("cached-checkout-123", "https://payment.example.com");
|
|
690
|
+
expect(getAccessTokenSpy).toHaveBeenCalledWith("https://resumed.example.com", "resume-token");
|
|
691
|
+
|
|
692
|
+
consoleSpy.mockRestore();
|
|
693
|
+
});
|
|
694
|
+
|
|
596
695
|
test("should handle URL validation edge cases", async () => {
|
|
597
696
|
loadConfigFromFileSpy.mockResolvedValue({});
|
|
598
697
|
mockOptions.prompts.select.mockResolvedValue("custom");
|
|
@@ -229,7 +229,7 @@ set -euo pipefail
|
|
|
229
229
|
function deploy_app() {
|
|
230
230
|
local app_name="$1"
|
|
231
231
|
local version="$2"
|
|
232
|
-
|
|
232
|
+
|
|
233
233
|
echo "Deploying $app_name version $version"
|
|
234
234
|
docker run --rm "$app_name:$version"
|
|
235
235
|
}
|
|
@@ -337,19 +337,6 @@ This document demonstrates various programming language code blocks.`;
|
|
|
337
337
|
});
|
|
338
338
|
});
|
|
339
339
|
|
|
340
|
-
describe("D2 syntax validation", () => {
|
|
341
|
-
test("should handle D2 syntax errors", async () => {
|
|
342
|
-
const documentStructure = [];
|
|
343
|
-
const reviewContent =
|
|
344
|
-
"```d2\n" +
|
|
345
|
-
"invalid d2 syntax {{\n" + // Malformed D2
|
|
346
|
-
"```\n\n" +
|
|
347
|
-
"This has proper structure.";
|
|
348
|
-
const result = await checkDetailResult({ documentStructure, reviewContent });
|
|
349
|
-
expect(result.isApproved).toBe(false);
|
|
350
|
-
});
|
|
351
|
-
});
|
|
352
|
-
|
|
353
340
|
describe("Advanced table edge cases", () => {
|
|
354
341
|
test("should handle empty table cells correctly", async () => {
|
|
355
342
|
const documentStructure = [];
|
|
@@ -660,7 +647,7 @@ flowchart TD
|
|
|
660
647
|
A["1. Create Backend Implementation<br>api/src/providers/"]
|
|
661
648
|
B["2. Add Backend Configuration<br>api/src/providers/models.ts"]
|
|
662
649
|
C["3. Update Frontend Selector<br>src/pages/config/ai-providers/"]
|
|
663
|
-
|
|
650
|
+
|
|
664
651
|
A --> B --> C
|
|
665
652
|
\`\`\`
|
|
666
653
|
|
|
@@ -183,12 +183,7 @@ describe("chooseDocs utility", () => {
|
|
|
183
183
|
locale: "en",
|
|
184
184
|
};
|
|
185
185
|
|
|
186
|
-
await expect(chooseDocs(input, mockOptions)).rejects.toThrow(
|
|
187
|
-
"Please provide a docs parameter to specify which documents to update",
|
|
188
|
-
);
|
|
189
|
-
expect(consoleErrorSpy).toHaveBeenCalledWith(
|
|
190
|
-
new Error("No documents found in the docs directory"),
|
|
191
|
-
);
|
|
186
|
+
await expect(chooseDocs(input, mockOptions)).rejects.toThrow();
|
|
192
187
|
});
|
|
193
188
|
|
|
194
189
|
test("should throw error when no documents selected interactively", async () => {
|
|
@@ -202,9 +197,7 @@ describe("chooseDocs utility", () => {
|
|
|
202
197
|
locale: "en",
|
|
203
198
|
};
|
|
204
199
|
|
|
205
|
-
await expect(chooseDocs(input, mockOptions)).rejects.toThrow(
|
|
206
|
-
"Please provide a docs parameter to specify which documents to update",
|
|
207
|
-
);
|
|
200
|
+
await expect(chooseDocs(input, mockOptions)).rejects.toThrow();
|
|
208
201
|
});
|
|
209
202
|
|
|
210
203
|
// CHECKBOX VALIDATION TESTS
|
|
@@ -336,7 +336,7 @@ describe("auth-utils", () => {
|
|
|
336
336
|
|
|
337
337
|
// Test that openPage calls the mock open function
|
|
338
338
|
await capturedOpenPage("https://auth.example.com");
|
|
339
|
-
expect(mockOpen).toHaveBeenCalledWith("https://auth.example.com
|
|
339
|
+
expect(mockOpen).toHaveBeenCalledWith("https://auth.example.com/?required_roles=owner%2Cadmin");
|
|
340
340
|
});
|
|
341
341
|
|
|
342
342
|
test("should handle authorization failure", async () => {
|
|
@@ -6,7 +6,7 @@ import path from "node:path";
|
|
|
6
6
|
|
|
7
7
|
import Debug from "debug";
|
|
8
8
|
|
|
9
|
-
import { TMP_ASSETS_DIR } from "../../utils/constants/index.mjs";
|
|
9
|
+
import { DOC_SMITH_DIR, TMP_ASSETS_DIR, TMP_DIR } from "../../utils/constants/index.mjs";
|
|
10
10
|
import {
|
|
11
11
|
beforePublishHook,
|
|
12
12
|
checkContent,
|
|
@@ -346,7 +346,7 @@ E -> F
|
|
|
346
346
|
try {
|
|
347
347
|
await checkContent({ content });
|
|
348
348
|
|
|
349
|
-
const assetDir = path.join(process.cwd(),
|
|
349
|
+
const assetDir = path.join(process.cwd(), DOC_SMITH_DIR, TMP_DIR, TMP_ASSETS_DIR, "d2");
|
|
350
350
|
const files = await readdir(assetDir);
|
|
351
351
|
const d2File = files.find((file) => file.endsWith(".d2"));
|
|
352
352
|
expect(d2File).toBeDefined();
|
|
@@ -365,7 +365,7 @@ E -> F
|
|
|
365
365
|
try {
|
|
366
366
|
await ensureTmpDir();
|
|
367
367
|
|
|
368
|
-
const tmpDir = path.join(tempDir,
|
|
368
|
+
const tmpDir = path.join(tempDir, DOC_SMITH_DIR, TMP_DIR);
|
|
369
369
|
const gitignorePath = path.join(tmpDir, ".gitignore");
|
|
370
370
|
|
|
371
371
|
expect(existsSync(tmpDir)).toBe(true);
|
|
@@ -386,7 +386,7 @@ E -> F
|
|
|
386
386
|
// First call
|
|
387
387
|
await ensureTmpDir();
|
|
388
388
|
|
|
389
|
-
const tmpDir = path.join(tempDir,
|
|
389
|
+
const tmpDir = path.join(tempDir, DOC_SMITH_DIR, TMP_DIR);
|
|
390
390
|
const gitignorePath = path.join(tmpDir, ".gitignore");
|
|
391
391
|
|
|
392
392
|
// Modify .gitignore to test if it gets overwritten
|