@aigne/doc-smith 0.8.3 → 0.8.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.
Files changed (58) hide show
  1. package/.aigne/doc-smith/config.yaml +3 -3
  2. package/.aigne/doc-smith/preferences.yml +58 -12
  3. package/.aigne/doc-smith/upload-cache.yaml +600 -207
  4. package/CHANGELOG.md +22 -0
  5. package/README.md +77 -5
  6. package/agents/input-generator.mjs +12 -6
  7. package/agents/publish-docs.mjs +53 -4
  8. package/docs/_sidebar.md +1 -1
  9. package/docs/advanced-how-it-works.md +55 -60
  10. package/docs/advanced-how-it-works.zh.md +60 -65
  11. package/docs/advanced-quality-assurance.md +73 -38
  12. package/docs/advanced-quality-assurance.zh.md +73 -38
  13. package/docs/advanced.md +2 -14
  14. package/docs/advanced.zh.md +5 -17
  15. package/docs/changelog.md +41 -4
  16. package/docs/changelog.zh.md +77 -40
  17. package/docs/cli-reference.md +79 -13
  18. package/docs/cli-reference.zh.md +92 -26
  19. package/docs/configuration-interactive-setup.md +102 -49
  20. package/docs/configuration-interactive-setup.zh.md +102 -49
  21. package/docs/configuration-language-support.md +69 -39
  22. package/docs/configuration-language-support.zh.md +68 -38
  23. package/docs/configuration-llm-setup.md +25 -62
  24. package/docs/configuration-llm-setup.zh.md +25 -62
  25. package/docs/configuration-preferences.md +79 -67
  26. package/docs/configuration-preferences.zh.md +78 -67
  27. package/docs/configuration.md +122 -109
  28. package/docs/configuration.zh.md +130 -117
  29. package/docs/features-generate-documentation.md +44 -24
  30. package/docs/features-generate-documentation.zh.md +52 -32
  31. package/docs/features-publish-your-docs.md +41 -40
  32. package/docs/features-publish-your-docs.zh.md +50 -49
  33. package/docs/features-translate-documentation.md +73 -17
  34. package/docs/features-translate-documentation.zh.md +76 -20
  35. package/docs/features-update-and-refine.md +72 -21
  36. package/docs/features-update-and-refine.zh.md +80 -29
  37. package/docs/features.md +24 -28
  38. package/docs/features.zh.md +25 -29
  39. package/docs/getting-started.md +87 -38
  40. package/docs/getting-started.zh.md +88 -39
  41. package/docs/overview.md +17 -35
  42. package/docs/overview.zh.md +18 -36
  43. package/package.json +9 -8
  44. package/prompts/content-detail-generator.md +1 -0
  45. package/prompts/document/custom-code-block.md +101 -0
  46. package/prompts/document/d2-chart/rules.md +941 -1031
  47. package/prompts/document/detail-generator.md +7 -53
  48. package/tests/input-generator.test.mjs +2 -2
  49. package/tests/kroki-utils.test.mjs +88 -17
  50. package/utils/auth-utils.mjs +9 -2
  51. package/utils/blocklet.mjs +25 -6
  52. package/utils/constants.mjs +17 -1
  53. package/utils/deploy.mjs +404 -0
  54. package/utils/kroki-utils.mjs +22 -14
  55. package/utils/markdown-checker.mjs +1 -1
  56. package/utils/utils.mjs +3 -2
  57. package/prompts/document/d2-chart/diy-examples.md +0 -44
  58. package/prompts/document/d2-chart/shape-rules.md +0 -182
@@ -0,0 +1,404 @@
1
+ import chalk from "chalk";
2
+ import { joinURL } from "ufo";
3
+ import { getComponentInfo, getComponentInfoWithMountPoint } from "./blocklet.mjs";
4
+ import { PAYMENT_KIT_DID } from "./constants.mjs";
5
+ import { saveValueToConfig } from "./utils.mjs";
6
+
7
+ // ==================== URL Configuration ====================
8
+ const BASE_URL = process.env.DOC_SMITH_BASE_URL || "";
9
+
10
+ // ==================== Timeout Configuration ====================
11
+ const INTERVAL_MS = 3000; // 3 seconds between each check
12
+ const TIMEOUTS = {
13
+ paymentWait: 300, // Step 2: Payment wait timeout (5 minutes)
14
+ installation: 300, // Step 3: Installation timeout (5 minutes)
15
+ serviceStart: 300, // Step 4: Service startup timeout (5 minutes)
16
+ };
17
+
18
+ // ==================== Utility Functions ====================
19
+
20
+ /**
21
+ * Generic polling utility with timeout and retry logic
22
+ * @param {Object} options - Polling configuration
23
+ * @param {Function} options.checkCondition - Async function that returns result if condition met, null/false if not
24
+ * @param {number} options.maxAttempts - Maximum number of attempts
25
+ * @param {number} options.intervalMs - Interval between attempts in milliseconds
26
+ * @param {string} options.timeoutMessage - Error message for timeout
27
+ * @param {string} options.stepName - Name of the step for logging (optional)
28
+ * @returns {Promise<any>} Result from checkCondition when successful
29
+ */
30
+ async function pollWithTimeout({
31
+ checkCondition,
32
+ maxAttempts,
33
+ intervalMs = INTERVAL_MS,
34
+ timeoutMessage,
35
+ stepName = "Operation",
36
+ }) {
37
+ let attempts = 0;
38
+
39
+ while (attempts < maxAttempts) {
40
+ attempts++;
41
+
42
+ try {
43
+ const result = await checkCondition();
44
+ if (result !== null && result !== false) {
45
+ return result;
46
+ }
47
+ } catch (_error) {
48
+ // Log error for debugging but continue retrying unless it's the last attempt
49
+ if (attempts === maxAttempts) {
50
+ throw new Error(`${timeoutMessage} (${stepName} failed after ${maxAttempts} attempts)`);
51
+ }
52
+ // Continue retrying for non-fatal errors
53
+ }
54
+
55
+ // If this is the last attempt, don't wait - just exit the loop
56
+ if (attempts === maxAttempts) {
57
+ break;
58
+ }
59
+
60
+ // Wait before retry
61
+ await new Promise((resolve) => setTimeout(resolve, intervalMs));
62
+ }
63
+
64
+ // If we reach here, all attempts were exhausted
65
+ throw new Error(`${timeoutMessage} (${stepName} timed out after ${maxAttempts} attempts)`);
66
+ }
67
+
68
+ // ==================== API Endpoints ====================
69
+ const API_ENDPOINTS = {
70
+ createCheckout: `/api/checkout-sessions/start`,
71
+ paymentPage: `/checkout/pay/{id}`,
72
+ orderStatus: `/api/vendors/order/{id}/status`,
73
+ orderDetail: `/api/vendors/order/{id}/detail`,
74
+ };
75
+
76
+ let prefix = "";
77
+ let paymentLinkId = "";
78
+ /**
79
+ * Deploy a new Discuss Kit service and return the installation URL
80
+ * @returns {Promise<string>} - The URL of the deployed service
81
+ */
82
+ export async function deploy(id, cachedUrl) {
83
+ const { mountPoint, PAYMENT_LINK_ID } = await getComponentInfoWithMountPoint(
84
+ BASE_URL,
85
+ PAYMENT_KIT_DID,
86
+ );
87
+ prefix = mountPoint;
88
+ paymentLinkId = PAYMENT_LINK_ID;
89
+
90
+ if (!PAYMENT_LINK_ID) {
91
+ const { PAYMENT_LINK_ID: id } = await getComponentInfoWithMountPoint(
92
+ joinURL(BASE_URL, mountPoint),
93
+ PAYMENT_KIT_DID,
94
+ );
95
+ paymentLinkId = id;
96
+ }
97
+
98
+ // Step 1: Create payment link and open
99
+ const cachedCheckoutId = await checkCacheCheckoutId(id);
100
+ let checkoutId = cachedCheckoutId;
101
+ let paymentUrl = cachedUrl;
102
+ if (!cachedCheckoutId) {
103
+ const { checkoutId: newCheckoutId, paymentUrl: newPaymentUrl } = await createPaymentSession();
104
+ checkoutId = newCheckoutId;
105
+ paymentUrl = newPaymentUrl;
106
+ }
107
+
108
+ if (!paymentUrl) {
109
+ paymentUrl = joinURL(BASE_URL, prefix, API_ENDPOINTS.paymentPage.replace("{id}", checkoutId));
110
+ }
111
+ if (cachedCheckoutId !== checkoutId) {
112
+ await openBrowser(paymentUrl);
113
+ }
114
+
115
+ // Step 2: Wait for payment completion
116
+ console.log(`${chalk.blue("⏳")} Step 1/4: Waiting for payment...`);
117
+ console.log(`${chalk.blue("🔗")} Payment link: ${chalk.cyan(paymentUrl)}\n`);
118
+ await pollPaymentStatus(checkoutId);
119
+ saveValueToConfig("checkoutId", checkoutId, "Checkout ID for document deployment service");
120
+ saveValueToConfig("paymentUrl", paymentUrl, "Payment URL for document deployment service");
121
+
122
+ // Step 3: Wait for service installation
123
+ console.log(`${chalk.blue("📦")} Step 2/4: Installing service...`);
124
+ const readyVendors = await waitInstallation(checkoutId);
125
+
126
+ // Step 4: Wait for service startup
127
+ console.log(`${chalk.blue("🚀")} Step 3/4: Starting service...`);
128
+ const runningVendors = await waitServiceRunning(readyVendors);
129
+
130
+ // Step 5: Get final URL
131
+ console.log(`${chalk.blue("🌐")} Step 4/4: Getting service URL...`);
132
+ const urlInfo = await getDashboardAndUrl(checkoutId, runningVendors);
133
+ const { appUrl, homeUrl, token } = urlInfo || {};
134
+
135
+ console.log(
136
+ `\n${chalk.blue("🔗")} Your website is available at: ${chalk.cyan(homeUrl || appUrl)}\n`,
137
+ );
138
+
139
+ return {
140
+ appUrl,
141
+ homeUrl,
142
+ token,
143
+ };
144
+ }
145
+
146
+ /**
147
+ * Check if there is a cached checkoutId
148
+ */
149
+ async function checkCacheCheckoutId(checkoutId) {
150
+ try {
151
+ if (!checkoutId) {
152
+ return "";
153
+ }
154
+
155
+ const orderStatusUrl = joinURL(
156
+ BASE_URL,
157
+ prefix,
158
+ API_ENDPOINTS.orderStatus.replace("{id}", checkoutId),
159
+ );
160
+ const response = await fetch(orderStatusUrl);
161
+
162
+ if (!response.ok) {
163
+ throw new Error(`HTTP error! status: ${response.status}`);
164
+ }
165
+
166
+ const data = await response.json();
167
+
168
+ if (data.error) {
169
+ throw new Error(data.error);
170
+ }
171
+
172
+ // Check payment status and vendors status
173
+ const isPaid = data.payment_status === "paid";
174
+
175
+ return isPaid ? checkoutId : "";
176
+ } catch (_error) {
177
+ saveValueToConfig("checkoutId", "", "Checkout ID for document deployment service");
178
+ return "";
179
+ }
180
+ }
181
+
182
+ /**
183
+ * Create payment session - Step 1
184
+ */
185
+ async function createPaymentSession() {
186
+ // 1. Call payment API
187
+ if (!paymentLinkId) {
188
+ throw new Error("Payment link ID not found");
189
+ }
190
+
191
+ const createCheckoutId = joinURL(BASE_URL, prefix, API_ENDPOINTS.createCheckout, paymentLinkId);
192
+ try {
193
+ const response = await fetch(createCheckoutId, {
194
+ method: "POST",
195
+ headers: { "Content-Type": "application/json" },
196
+ body: JSON.stringify({
197
+ needShortUrl: true,
198
+ metadata: {
199
+ page_info: {
200
+ has_vendor: true,
201
+ success_message: {
202
+ en: "Congratulations! Your website has been successfully installed. You can return to the command-line tool to continue the next steps.",
203
+ zh: "恭喜您,你的网站已安装成功!可以返回命令行工具继续后续操作!",
204
+ },
205
+ },
206
+ },
207
+ }),
208
+ });
209
+
210
+ if (!response.ok) {
211
+ throw new Error(`HTTP error! status: ${response.status}`);
212
+ }
213
+
214
+ const data = await response.json();
215
+ const checkoutId = data.checkoutSession.id;
216
+ const paymentUrl = data.paymentUrl;
217
+
218
+ return { checkoutId, paymentUrl };
219
+ } catch (error) {
220
+ console.error(
221
+ `${chalk.red("❌")} Failed to create payment session:`,
222
+ error.message,
223
+ createCheckoutId,
224
+ );
225
+ throw new Error(`Failed to create payment session: ${error.message}`);
226
+ }
227
+ }
228
+
229
+ /**
230
+ * Open browser with payment URL
231
+ */
232
+ async function openBrowser(paymentUrl) {
233
+ const { default: open } = await import("open");
234
+ try {
235
+ await open(paymentUrl);
236
+ } catch (_error) {
237
+ console.log(`${chalk.yellow("⚠️ Could not open browser automatically.")}`);
238
+ console.log(`${chalk.blue("Please manually open this URL:")} ${chalk.cyan(paymentUrl)}`);
239
+ }
240
+ }
241
+
242
+ /**
243
+ * Wait for payment completion - Step 2 (5 minute timeout)
244
+ */
245
+ async function pollPaymentStatus(checkoutId) {
246
+ return pollWithTimeout({
247
+ checkCondition: async () => {
248
+ const orderStatusUrl = joinURL(
249
+ BASE_URL,
250
+ prefix,
251
+ API_ENDPOINTS.orderStatus.replace("{id}", checkoutId),
252
+ );
253
+ const response = await fetch(orderStatusUrl);
254
+
255
+ if (!response.ok) {
256
+ throw new Error(`HTTP error! status: ${response.status}`);
257
+ }
258
+
259
+ const data = await response.json();
260
+
261
+ if (data.error) {
262
+ throw new Error(data.error);
263
+ }
264
+
265
+ // Check payment status and vendors status
266
+ const isPaid = data.payment_status === "paid";
267
+ if (isPaid) {
268
+ return data.vendors;
269
+ }
270
+
271
+ return null; // Not ready yet, continue polling
272
+ },
273
+ maxAttempts: Math.ceil((TIMEOUTS.paymentWait * 1000) / INTERVAL_MS),
274
+ intervalMs: INTERVAL_MS,
275
+ timeoutMessage: "Payment timeout - please complete payment within 5 minutes",
276
+ stepName: "Payment",
277
+ });
278
+ }
279
+
280
+ /**
281
+ * Wait for installation completion - Step 3
282
+ */
283
+ async function waitInstallation(checkoutId) {
284
+ return pollWithTimeout({
285
+ checkCondition: async () => {
286
+ const orderStatusUrl = joinURL(
287
+ BASE_URL,
288
+ prefix,
289
+ API_ENDPOINTS.orderStatus.replace("{id}", checkoutId),
290
+ );
291
+ const response = await fetch(orderStatusUrl);
292
+
293
+ if (!response.ok) {
294
+ throw new Error(`HTTP error! status: ${response.status}`);
295
+ }
296
+
297
+ const data = await response.json();
298
+
299
+ if (data.error) {
300
+ throw new Error(data.error);
301
+ }
302
+
303
+ // Check if all vendors meet conditions: progress >= 80 and appUrl exists
304
+ const isInstalled = data.vendors?.every((vendor) => vendor.progress >= 80 && vendor.appUrl);
305
+ if (isInstalled) {
306
+ return data.vendors;
307
+ }
308
+
309
+ return null; // Not ready yet, continue polling
310
+ },
311
+ maxAttempts: Math.ceil((TIMEOUTS.installation * 1000) / INTERVAL_MS),
312
+ intervalMs: INTERVAL_MS,
313
+ timeoutMessage: "Installation timeout - services failed to install within 5 minutes",
314
+ stepName: "Installation",
315
+ });
316
+ }
317
+
318
+ /**
319
+ * Wait for service to start running - Step 4
320
+ */
321
+ async function waitServiceRunning(readyVendors) {
322
+ return pollWithTimeout({
323
+ checkCondition: async () => {
324
+ // Check running status of all vendors concurrently
325
+ const vendorChecks = readyVendors.map(async (vendor) => {
326
+ try {
327
+ const blockletInfo = await getComponentInfo(vendor.appUrl);
328
+
329
+ if (blockletInfo.status === "running") {
330
+ return vendor;
331
+ }
332
+ return null;
333
+ } catch (_error) {
334
+ return null;
335
+ }
336
+ });
337
+
338
+ const results = await Promise.all(vendorChecks);
339
+ const runningVendors = results.filter((vendor) => vendor !== null);
340
+
341
+ if (runningVendors.length === readyVendors.length) {
342
+ return runningVendors;
343
+ }
344
+
345
+ return null; // Not ready yet, continue polling
346
+ },
347
+ maxAttempts: Math.ceil((TIMEOUTS.serviceStart * 1000) / INTERVAL_MS),
348
+ intervalMs: INTERVAL_MS,
349
+ timeoutMessage: "Service start timeout - services failed to start within 5 minutes",
350
+ stepName: "Service Start",
351
+ });
352
+ }
353
+
354
+ /**
355
+ * Get final URL - Step 5
356
+ */
357
+ async function getDashboardAndUrl(checkoutId, runningVendors) {
358
+ try {
359
+ // 5. Get order details
360
+ const orderDetailUrl = joinURL(
361
+ BASE_URL,
362
+ prefix,
363
+ API_ENDPOINTS.orderDetail.replace("{id}", checkoutId),
364
+ );
365
+ const response = await fetch(orderDetailUrl);
366
+
367
+ if (!response.ok) {
368
+ throw new Error(`HTTP error! status: ${response.status}`);
369
+ }
370
+
371
+ const data = await response.json();
372
+
373
+ if (data.vendors.length === 0) {
374
+ throw new Error("No vendors found in order details");
375
+ }
376
+
377
+ // Wait 3 seconds
378
+ await new Promise((resolve) => setTimeout(resolve, 3000));
379
+
380
+ // Return the appUrl of the first vendor (usually only one)
381
+ const appUrl = runningVendors[0]?.appUrl;
382
+ if (!appUrl) {
383
+ throw new Error("No app URL found in order details");
384
+ }
385
+
386
+ return {
387
+ appUrl,
388
+ dashboardUrl: data.vendors[0]?.dashboardUrl,
389
+ homeUrl: data.vendors[0]?.homeUrl,
390
+ token: data.vendors[0]?.token,
391
+ };
392
+ } catch (error) {
393
+ console.error(`${chalk.red("❌")} Failed to get order details:`, error.message);
394
+ // If getting details fails, use the appUrl of running vendor
395
+ return {
396
+ appUrl: runningVendors[0]?.appUrl || null,
397
+ dashboardUrl: runningVendors[0]?.dashboardUrl || null,
398
+ homeUrl: runningVendors[0]?.homeUrl || null,
399
+ token: runningVendors[0]?.token || null,
400
+ };
401
+ }
402
+ }
403
+
404
+ export default deploy;
@@ -1,5 +1,6 @@
1
1
  import path from "node:path";
2
2
 
3
+ import Debug from "debug";
3
4
  import fs from "fs-extra";
4
5
  import { glob } from "glob";
5
6
  import pMap from "p-map";
@@ -14,6 +15,8 @@ import {
14
15
  } from "./constants.mjs";
15
16
  import { getContentHash } from "./utils.mjs";
16
17
 
18
+ const debug = Debug("doc-smith");
19
+
17
20
  export async function getChart({ chart = "d2", format = "svg", content, strict }) {
18
21
  const baseUrl = "https://chart.abtnet.io";
19
22
 
@@ -56,7 +59,7 @@ export async function saveD2Assets({ markdown, docsDir }) {
56
59
  return markdown;
57
60
  }
58
61
 
59
- const codeBlockRegex = /```d2\n([\s\S]*?)```/g;
62
+ const codeBlockRegex = /```d2.*\n([\s\S]*?)```/g;
60
63
 
61
64
  const { replaced } = await runIterator({
62
65
  input: markdown,
@@ -70,26 +73,26 @@ export async function saveD2Assets({ markdown, docsDir }) {
70
73
  const svgPath = path.join(assetDir, fileName);
71
74
 
72
75
  if (await fs.pathExists(svgPath)) {
73
- if (process.env.DEBUG) {
74
- console.log("Found assets cache, skipping generation", svgPath);
75
- }
76
+ debug("Found assets cache, skipping generation", svgPath);
76
77
  } else {
77
- if (process.env.DEBUG) {
78
- console.log("start generate d2 chart", svgPath);
79
- }
80
78
  try {
79
+ debug("start generate d2 chart", svgPath);
80
+ if (debug.enabled) {
81
+ const d2FileName = `${getContentHash(d2Content)}.d2`;
82
+ const d2Path = path.join(assetDir, d2FileName);
83
+ await fs.writeFile(d2Path, d2Content, { encoding: "utf8" });
84
+ }
85
+
81
86
  const svg = await getD2Svg({ content: d2Content });
82
87
  if (svg) {
83
88
  await fs.writeFile(svgPath, svg, { encoding: "utf8" });
84
89
  }
85
90
  } catch (error) {
86
- if (process.env.DEBUG) {
87
- console.warn("Failed to generate D2 chart:", error);
88
- }
91
+ debug("Failed to generate D2 chart:", error);
89
92
  return _code;
90
93
  }
91
94
  }
92
- return `![](../${TMP_ASSETS_DIR}/d2/${fileName})`;
95
+ return `![](${path.posix.join("..", TMP_ASSETS_DIR, "d2", fileName)})`;
93
96
  },
94
97
  options: { concurrency: KROKI_CONCURRENCY },
95
98
  });
@@ -146,10 +149,15 @@ export async function checkD2Content({ content }) {
146
149
  const d2Content = [D2_CONFIG, content].join("\n");
147
150
  const fileName = `${getContentHash(d2Content)}.svg`;
148
151
  const svgPath = path.join(assetDir, fileName);
152
+
153
+ if (debug.enabled) {
154
+ const d2FileName = `${getContentHash(d2Content)}.d2`;
155
+ const d2Path = path.join(assetDir, d2FileName);
156
+ await fs.writeFile(d2Path, d2Content, { encoding: "utf8" });
157
+ }
158
+
149
159
  if (await fs.pathExists(svgPath)) {
150
- if (process.env.DEBUG) {
151
- console.log("Found assets cache, skipping generation", svgPath);
152
- }
160
+ debug("Found assets cache, skipping generation", svgPath);
153
161
  return;
154
162
  }
155
163
 
@@ -243,7 +243,7 @@ function checkLocalImages(markdown, source, errorMessages, markdownFilePath, bas
243
243
  */
244
244
  function checkContentStructure(markdown, source, errorMessages) {
245
245
  const lines = markdown.split("\n");
246
- const allCodeBlockRegex = /^\s*```(?:[a-zA-Z0-9_,\-+.#/:=]+)?$/;
246
+ const allCodeBlockRegex = /^\s*```.*$/;
247
247
 
248
248
  // State variables for different checks
249
249
  let inCodeBlock = false;
package/utils/utils.mjs CHANGED
@@ -1158,6 +1158,7 @@ export function detectSystemLanguage() {
1158
1158
  }
1159
1159
  }
1160
1160
 
1161
- export function getContentHash(str) {
1162
- return crypto.createHash("sha256").update(str).digest("hex");
1161
+ export function getContentHash(str, { trim = true } = {}) {
1162
+ const input = trim && typeof str === "string" ? str.trim() : str;
1163
+ return crypto.createHash("sha256").update(input).digest("hex");
1163
1164
  }
@@ -1,44 +0,0 @@
1
- - 结构图示例:
2
- ```d2
3
- App: Application
4
- API: API Server
5
- DB: Database
6
-
7
- App -> API: 调用
8
- API -> DB: 读写数据
9
- ```
10
- - 流程图示例:
11
- ```d2
12
- start: 开始
13
- input: 用户输入
14
- process: 处理数据
15
- output: 输出结果
16
- end: 结束
17
-
18
- start -> input -> process -> output -> end
19
- ```
20
- - 时序图示例:
21
- ```d2
22
- User: 用户
23
- Service: 服务
24
- DB: 数据库
25
-
26
- User -> Service: 请求
27
- Service -> DB: 查询
28
- DB -> Service: 返回数据
29
- Service -> User: 响应
30
- ```
31
- - 决策树示例:
32
- ```d2
33
- start: 开始
34
- check: 是否有效?
35
- yes: 是
36
- no: 否
37
- end: 结束
38
-
39
- start -> check
40
- check -> yes: 有效
41
- check -> no: 无效
42
- yes -> end
43
- no -> end
44
- ```