@aigne/doc-smith 0.8.11-beta.4 → 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.
Files changed (31) hide show
  1. package/.aigne/doc-smith/config.yaml +1 -3
  2. package/.release-please-manifest.json +1 -1
  3. package/CHANGELOG.md +8 -0
  4. package/agents/generate/check-d2-diagram-valid.mjs +26 -0
  5. package/agents/generate/generate-d2-diagram.yaml +23 -0
  6. package/agents/generate/merge-d2-diagram.yaml +39 -0
  7. package/agents/init/index.mjs +18 -10
  8. package/agents/publish/publish-docs.mjs +17 -20
  9. package/agents/update/generate-document.yaml +25 -0
  10. package/package.json +2 -2
  11. package/prompts/detail/{d2-chart/rules.md → d2-diagram/rules-system.md} +41 -5
  12. package/prompts/detail/d2-diagram/rules-user.md +4 -0
  13. package/prompts/detail/document-rules.md +2 -3
  14. package/prompts/detail/generate-document.md +8 -2
  15. package/prompts/detail/update-document.md +0 -2
  16. package/tests/agents/init/init.test.mjs +25 -19
  17. package/tests/agents/publish/publish-docs.test.mjs +99 -0
  18. package/tests/agents/utils/check-detail-result.test.mjs +2 -15
  19. package/tests/utils/auth-utils.test.mjs +1 -1
  20. package/tests/utils/d2-utils.test.mjs +4 -4
  21. package/tests/utils/deploy.test.mjs +3 -10
  22. package/tests/utils/kroki-utils.test.mjs +5 -5
  23. package/tests/utils/preferences-utils.test.mjs +5 -3
  24. package/tests/utils/save-value-to-config.test.mjs +3 -1
  25. package/utils/auth-utils.mjs +4 -0
  26. package/utils/constants/index.mjs +3 -0
  27. package/utils/d2-utils.mjs +11 -6
  28. package/utils/deploy.mjs +4 -20
  29. package/utils/kroki-utils.mjs +5 -4
  30. package/utils/markdown-checker.mjs +0 -20
  31. /package/prompts/detail/{d2-chart → d2-diagram}/official-examples.md +0 -0
@@ -67,8 +67,6 @@ docsDir: ./docs # Directory to save generated documentation
67
67
  sourcesPath: # Source code paths to analyze
68
68
  - ./
69
69
  lastGitHead: f3f14b93e6b2e6beb42b2f368c2560ab050dfd03
70
- appUrl: https://docsmith.aigne.io
71
70
  # ⚠️ Warning: boardId is auto-generated by system, please do not edit manually
72
71
  boardId: "docsmith"
73
- # Checkout ID for document deployment service
74
- checkoutId: ""
72
+ appUrl: https://docsmith.aigne.io
@@ -1,3 +1,3 @@
1
1
  {
2
- ".": "0.8.11-beta.4"
2
+ ".": "0.8.11-beta.5"
3
3
  }
package/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.8.11-beta.5](https://github.com/AIGNE-io/aigne-doc-smith/compare/v0.8.11-beta.4...v0.8.11-beta.5) (2025-10-01)
4
+
5
+
6
+ ### Features
7
+
8
+ * add user role requirement for publish and custom rule support ([#151](https://github.com/AIGNE-io/aigne-doc-smith/issues/151)) ([95866f9](https://github.com/AIGNE-io/aigne-doc-smith/commit/95866f9fcb2ca697da42e950e9011b29913726d4))
9
+ * split d2 diagram generate as independent tool ([#152](https://github.com/AIGNE-io/aigne-doc-smith/issues/152)) ([8e9b811](https://github.com/AIGNE-io/aigne-doc-smith/commit/8e9b811dc6108bb19ab8a1853afb4cab92af1d62))
10
+
3
11
  ## [0.8.11-beta.4](https://github.com/AIGNE-io/aigne-doc-smith/compare/v0.8.11-beta.3...v0.8.11-beta.4) (2025-09-30)
4
12
 
5
13
 
@@ -0,0 +1,26 @@
1
+ import { checkContent } from "../../utils/d2-utils.mjs";
2
+
3
+ export default async function checkD2DiagramIsValid({ d2DiagramSourceCode }) {
4
+ try {
5
+ await checkContent({ content: d2DiagramSourceCode });
6
+ return {
7
+ isValid: true,
8
+ };
9
+ } catch (err) {
10
+ return {
11
+ isValid: false,
12
+ error: err.message,
13
+ };
14
+ }
15
+ }
16
+
17
+ checkD2DiagramIsValid.input_schema = {
18
+ type: "object",
19
+ properties: {
20
+ d2DiagramSourceCode: {
21
+ type: "string",
22
+ description: "Source code of d2 diagram",
23
+ },
24
+ },
25
+ required: ["d2DiagramSourceCode"],
26
+ };
@@ -0,0 +1,23 @@
1
+ name: drawD2Diagram
2
+ description: Generate a D2 diagram from document content.
3
+ instructions:
4
+ - role: system
5
+ url: ../../prompts/detail/d2-diagram/rules-system.md
6
+ - role: user
7
+ url: ../../prompts/detail/d2-diagram/rules-user.md
8
+ input_schema:
9
+ type: object
10
+ properties:
11
+ documentContent:
12
+ type: string
13
+ description: Source code of current document (without the D2 diagram)
14
+ required:
15
+ - documentContent
16
+ output_schema:
17
+ type: object
18
+ properties:
19
+ d2DiagramSourceCode:
20
+ type: string
21
+ description: Source code to draw D2 diagram
22
+ required:
23
+ - d2DiagramSourceCode
@@ -0,0 +1,39 @@
1
+ name: mergeD2DiagramToDocument
2
+ description: Merge D2 Diagram source code into document
3
+ instructions: |
4
+ You are an AI assistant that helps to merge d2 diagram into document.
5
+
6
+ <datasources>
7
+ {{ content }}
8
+ </datasources>
9
+ <datasources>
10
+ {{ d2DiagramSourceCode }}
11
+ </datasources>
12
+
13
+ Given the source content of a document and the D2 diagram source code, your task is to:
14
+ - **Keep the original content as soon as possible.**
15
+ - D2 diagram source code should wrap by ```d2 and ``` in markdown format.
16
+ - Should find proper position to insert the D2 diagram in the document, usually after the first paragraph or after the section that describes the diagram.
17
+ - If there is no suitable position, append it to the end of the document.
18
+ - If there is no D2 diagram source code, return the original document content without any changes.
19
+ input_schema:
20
+ type: object
21
+ properties:
22
+ content:
23
+ type: string
24
+ description: Source content of the document
25
+ d2DiagramSourceCode:
26
+ type: string
27
+ description: Source content of D2 Diagram
28
+ required:
29
+ - content
30
+ - d2DiagramSourceCode
31
+ output_schema:
32
+ type: object
33
+ properties:
34
+ content:
35
+ type: string
36
+ description: Merged content of the document with D2 diagram
37
+ required:
38
+ - content
39
+
@@ -76,7 +76,7 @@ export default async function init(
76
76
 
77
77
  // 1. Primary purpose - what's the main outcome you want readers to achieve?
78
78
  const purposeChoices = await options.prompts.checkbox({
79
- message: "📝 [1/8]: What should your documentation help readers achieve?",
79
+ message: "📝 [1/9]: What should your documentation help readers achieve?",
80
80
  choices: Object.entries(DOCUMENT_STYLES)
81
81
  .filter(([key]) => key !== "custom") // Remove custom option for multiselect
82
82
  .map(([key, style]) => ({
@@ -124,7 +124,7 @@ export default async function init(
124
124
 
125
125
  // 2. Target audience - who will be reading this most often?
126
126
  const audienceChoices = await options.prompts.checkbox({
127
- message: "👥 [2/8]: Who will be reading your documentation?",
127
+ message: "👥 [2/9]: Who will be reading your documentation?",
128
128
  choices: Object.entries(TARGET_AUDIENCES)
129
129
  .filter(([key]) => key !== "custom") // Remove custom option for multiselect
130
130
  .map(([key, audience]) => ({
@@ -143,7 +143,15 @@ export default async function init(
143
143
  // Save target audience choices as keys
144
144
  input.targetAudienceTypes = audienceChoices;
145
145
 
146
- // 3. Reader knowledge level - what do readers typically know when they arrive?
146
+ // 3. Custom rules - any specific requirements for the documentation?
147
+ const rulesInput = await options.prompts.input({
148
+ message:
149
+ "📋 [3/9]: Any custom rules or requirements for your documentation? (Optional, press Enter to skip)",
150
+ default: "",
151
+ });
152
+ input.rules = rulesInput.trim();
153
+
154
+ // 4. Reader knowledge level - what do readers typically know when they arrive?
147
155
  // Determine default based on selected purposes using mapping
148
156
  const mappedPurpose = prioritizedPurposes.find(
149
157
  (purpose) => PURPOSE_TO_KNOWLEDGE_MAPPING[purpose],
@@ -158,7 +166,7 @@ export default async function init(
158
166
  );
159
167
 
160
168
  const knowledgeChoice = await options.prompts.select({
161
- message: "🧠 [3/8]: How much do readers already know about your project?",
169
+ message: "🧠 [4/9]: How much do readers already know about your project?",
162
170
  choices: Object.entries(filteredKnowledgeOptions).map(([key, level]) => ({
163
171
  name: `${level.name}`,
164
172
  description: level.description,
@@ -203,7 +211,7 @@ export default async function init(
203
211
  );
204
212
 
205
213
  const depthChoice = await options.prompts.select({
206
- message: "📊 [4/8]: How detailed should your documentation be?",
214
+ message: "📊 [5/9]: How detailed should your documentation be?",
207
215
  choices: Object.entries(filteredDepthOptions).map(([key, depth]) => ({
208
216
  name: `${depth.name}`,
209
217
  description: depth.description,
@@ -221,7 +229,7 @@ export default async function init(
221
229
 
222
230
  // Let user select primary language from supported list
223
231
  const primaryLanguageChoice = await options.prompts.select({
224
- message: "🌐 [5/8]: What's your main documentation language?",
232
+ message: "🌐 [6/9]: What's your main documentation language?",
225
233
  choices: SUPPORTED_LANGUAGES.map((lang) => ({
226
234
  name: `${lang.label} - ${lang.sample}`,
227
235
  value: lang.code,
@@ -238,7 +246,7 @@ export default async function init(
238
246
  );
239
247
 
240
248
  const translateLanguageChoices = await options.prompts.checkbox({
241
- message: "🔄 [6/8]: Which languages should we translate to?",
249
+ message: "🔄 [7/9]: Which languages should we translate to?",
242
250
  choices: availableTranslationLanguages.map((lang) => ({
243
251
  name: `${lang.label} - ${lang.sample}`,
244
252
  value: lang.code,
@@ -249,13 +257,13 @@ export default async function init(
249
257
 
250
258
  // 7. Documentation directory
251
259
  const docsDirInput = await options.prompts.input({
252
- message: `📁 [7/8]: Where should we save your documentation?`,
260
+ message: `📁 [8/9]: Where should we save your documentation?`,
253
261
  default: `${outputPath}/docs`,
254
262
  });
255
263
  input.docsDir = docsDirInput.trim() || `${outputPath}/docs`;
256
264
 
257
265
  // 8. Content sources
258
- console.log("\n🔍 [8/8]: Content Sources");
266
+ console.log("\n🔍 [9/9]: Content Sources");
259
267
  console.log(
260
268
  "What folders/files should we analyze for documentation? (e.g., ./src, ./docs, ./README.md)",
261
269
  );
@@ -400,7 +408,7 @@ export function generateYAML(input) {
400
408
  documentationDepth: input.documentationDepth || "",
401
409
 
402
410
  // Custom rules and target audience (empty for user to fill)
403
- rules: "",
411
+ rules: input.rules || "",
404
412
  targetAudience: "",
405
413
 
406
414
  // Language settings
@@ -5,13 +5,17 @@ import chalk from "chalk";
5
5
  import fs from "fs-extra";
6
6
 
7
7
  import { getAccessToken } from "../../utils/auth-utils.mjs";
8
- import { DISCUSS_KIT_STORE_URL, TMP_DIR, TMP_DOCS_DIR } from "../../utils/constants/index.mjs";
8
+ import {
9
+ DEFAULT_APP_URL,
10
+ DISCUSS_KIT_STORE_URL,
11
+ DOC_SMITH_DIR,
12
+ TMP_DIR,
13
+ TMP_DOCS_DIR,
14
+ } from "../../utils/constants/index.mjs";
9
15
  import { beforePublishHook, ensureTmpDir } from "../../utils/d2-utils.mjs";
10
16
  import { deploy } from "../../utils/deploy.mjs";
11
17
  import { getGithubRepoUrl, loadConfigFromFile, saveValueToConfig } from "../../utils/utils.mjs";
12
18
 
13
- const DEFAULT_APP_URL = "https://docsmith.aigne.io";
14
-
15
19
  export default async function publishDocs(
16
20
  { docsDir: rawDocsDir, appUrl, boardId, projectName, projectDesc, projectLogo },
17
21
  options,
@@ -19,9 +23,7 @@ export default async function publishDocs(
19
23
  // move work dir to tmp-dir
20
24
  await ensureTmpDir();
21
25
 
22
- const hasDocSmithBaseUrl = !!process.env.DOC_SMITH_BASE_URL;
23
-
24
- const docsDir = join(".aigne", "doc-smith", TMP_DIR, TMP_DOCS_DIR);
26
+ const docsDir = join(DOC_SMITH_DIR, TMP_DIR, TMP_DOCS_DIR);
25
27
  await fs.rm(docsDir, { recursive: true, force: true });
26
28
  await fs.mkdir(docsDir, {
27
29
  recursive: true,
@@ -61,22 +63,18 @@ export default async function publishDocs(
61
63
  name: `${chalk.blue("Your existing website")} - Integrate and publish directly on your current site (setup required)`,
62
64
  value: "custom",
63
65
  },
64
- ...(hasCachedCheckoutId && hasDocSmithBaseUrl
66
+ ...(hasCachedCheckoutId
65
67
  ? [
66
68
  {
67
- name: `${chalk.yellow("Resume previous website setup")} - ${chalk.green("Already paid.")} Continue where you left off. Your payment is already processed.`,
69
+ name: `${chalk.yellow("Resume previous website setup")} - ${chalk.green("Already paid.")} Continue where you left off. Your payment has already been processed.`,
68
70
  value: "new-instance-continue",
69
71
  },
70
72
  ]
71
73
  : []),
72
- ...(hasDocSmithBaseUrl
73
- ? [
74
- {
75
- name: `${chalk.blue("New website")} - ${chalk.yellow("Paid service.")} We'll help you set up a brand-new website with custom domain and hosting. Great if you want a professional presence.`,
76
- value: "new-instance",
77
- },
78
- ]
79
- : []),
74
+ {
75
+ name: `${chalk.blue("New website")} - ${chalk.yellow("Paid service.")} We'll help you set up a brand-new website with custom domain and hosting. Great if you want a professional presence.`,
76
+ value: "new-instance",
77
+ },
80
78
  ],
81
79
  });
82
80
 
@@ -100,7 +98,7 @@ export default async function publishDocs(
100
98
  });
101
99
  // Ensure appUrl has protocol
102
100
  appUrl = userInput.includes("://") ? userInput : `https://${userInput}`;
103
- } else if (hasDocSmithBaseUrl && ["new-instance", "new-instance-continue"].includes(choice)) {
101
+ } else if (["new-instance", "new-instance-continue"].includes(choice)) {
104
102
  // Deploy a new Discuss Kit service
105
103
  try {
106
104
  let id = "";
@@ -118,8 +116,7 @@ export default async function publishDocs(
118
116
  token = ltToken;
119
117
  } catch (error) {
120
118
  const errorMsg = error?.message || "Unknown error occurred";
121
- console.error(`${chalk.red("❌ Failed to publish to website:")} ${errorMsg}`);
122
- return { message: `❌ Publish failed: ${errorMsg}` };
119
+ return { message: `${chalk.red("❌ Failed to publish to website:")} ${errorMsg}` };
123
120
  }
124
121
  }
125
122
  }
@@ -168,7 +165,7 @@ export default async function publishDocs(
168
165
  boardDesc: projectInfo.description,
169
166
  boardCover: projectInfo.icon,
170
167
  mediaFolder: rawDocsDir,
171
- cacheFilePath: join(".aigne", "doc-smith", "upload-cache.yaml"),
168
+ cacheFilePath: join(DOC_SMITH_DIR, "upload-cache.yaml"),
172
169
  boardMeta,
173
170
  });
174
171
 
@@ -48,3 +48,28 @@ output_schema:
48
48
  type: string
49
49
  required:
50
50
  - content
51
+ skills:
52
+ - type: team
53
+ task_render_mode: collapse
54
+ name: generateD2DiagramContent
55
+ skills:
56
+ - ../generate/generate-d2-diagram.yaml
57
+ reflection:
58
+ reviewer: ../generate/check-d2-diagram-valid.mjs
59
+ is_approved: isValid
60
+ max_iterations: 5
61
+ return_last_on_max_iterations: true
62
+ input_schema:
63
+ type: object
64
+ properties:
65
+ documentContent:
66
+ type: string
67
+ description: Source code of current document (without the D2 diagram)
68
+ required:
69
+ - documentContent
70
+ output_schema:
71
+ type: object
72
+ properties:
73
+ d2DiagramSourceCode:
74
+ type: string
75
+ description: Source code for the D2 diagram
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aigne/doc-smith",
3
- "version": "0.8.11-beta.4",
3
+ "version": "0.8.11-beta.5",
4
4
  "description": "AI-driven documentation generation tool built on the AIGNE Framework",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -19,7 +19,7 @@
19
19
  "@aigne/gemini": "^0.14.0",
20
20
  "@aigne/openai": "^0.16.0",
21
21
  "@aigne/publish-docs": "^0.11.0",
22
- "@blocklet/payment-broker-client": "^1.20.20",
22
+ "@blocklet/payment-broker-client": "^1.21.5",
23
23
  "@terrastruct/d2": "^0.1.33",
24
24
  "chalk": "^5.5.0",
25
25
  "cli-highlight": "^2.1.11",
@@ -1,12 +1,51 @@
1
- <d2_generate_constraints>
2
1
  # D2 Diagram Generation Expert Guide
3
2
 
4
3
  ## Preamble: LLM Role and Core Objective
5
4
 
6
- You are an expert software architect and a master of the D2 (Declarative Diagramming) language. Your primary function is to translate abstract descriptions of software systems, components, and processes into precise, readable, and visually effective D2 diagram code.
5
+ You are an AI diagram generator with the personality of an **ISTJ (The Logistician)**.
6
+ You are reliable, rule-abiding, and methodical. Your goal is to produce **clear, precise, and logically structured d2-diagram code** that accurately represents the given description.
7
7
 
8
8
  Your core directive is to produce D2 code that is not only syntactically correct but also semantically meaningful and adheres to the highest standards of technical diagramming. The generated output must follow all instructions, constraints, and best practices detailed in this document. You will operate in a zero-tolerance mode for syntactical errors, especially concerning predefined keyword values. The fundamental principle is the separation of concerns: the logical structure of the diagram must be defined independently of its visual styling. The following chapters are structured to enforce this principle.
9
9
 
10
+ You value **order, consistency, and factual accuracy** over abstract or decorative styles.
11
+ Your diagrams should focus on **readability, structural correctness, and practical use in technical documentation**.
12
+
13
+ **ISTJ-style guiding principles for your diagram generation:**
14
+
15
+ 1. **Fact-Driven and Accurate:**
16
+ - Adhere strictly to the provided description and rules.
17
+ - Do not assume or add elements that are not explicitly described.
18
+
19
+ 2. **Structured and Orderly:**
20
+ - Organize diagram elements in a logical, hierarchical order (e.g., top-down for processes, left-to-right for data flows).
21
+ - Group related nodes consistently.
22
+ - Maintain clear flow and avoid unnecessary crossing lines.
23
+
24
+ 3. **Clarity and Precision:**
25
+ - Use simple, standard shapes and consistent node labeling.
26
+ - Ensure every arrow or connection has a clear meaning (e.g., data flow, control flow).
27
+ - Avoid ambiguous or decorative text.
28
+
29
+ 4. **Standards and Consistency:**
30
+ - Follow best practices for technical diagrams (e.g., rectangular boxes for processes, cylinders for data storage, labeled arrows for flows).
31
+ - Maintain consistent spacing, alignment, and sizing in the diagram code.
32
+
33
+ 5. **Practical and Maintainable:**
34
+ - Ensure the generated d2 code is concise, easy to edit, and reproducible.
35
+ - Provide comments in the d2 code (if necessary) to clarify sections or complex relationships.
36
+ - Avoid unnecessary stylistic complexity that may hinder future maintenance.
37
+
38
+ **Output Requirements:**
39
+ - Output only valid d2-diagram code.
40
+ - Do not include explanatory text outside of the code block.
41
+ - Ensure the diagram reflects a clean, professional, ISTJ-style technical drawing.
42
+ - output must be wrap with
43
+ ```md
44
+ \`\`\`d2\n...\n\`\`\`
45
+ ```
46
+
47
+
48
+
10
49
  ## Chapter 1: Core Instructions for D2 Diagram Generation
11
50
 
12
51
  This chapter establishes the foundational rules for generating the structure and logic of a D2 diagram. It prioritizes semantic correctness and adherence to diagramming principles over aesthetic concerns, which are addressed in Chapter 2.
@@ -1090,6 +1129,3 @@ Session.t2 -> Session.t2: "Update and remove status effects"
1090
1129
  Session.t2 -> Lua: "Trigger OnPlayerTurn"
1091
1130
  User.t2 <- Session.t2
1092
1131
  ```
1093
-
1094
-
1095
- </d2_generate_constraints>
@@ -0,0 +1,4 @@
1
+ Follow the given rules and ISTJ style from your system instructions.
2
+
3
+ Generate a d2-diagram that represents the following document content:
4
+ {{documentContent}}
@@ -19,9 +19,8 @@ Documentation Generation Rules:
19
19
  - When describing multiple properties of the same object, wrap the outermost `<x-field>` elements with `<x-field-group>` elements. Note that nested `<x-field>` elements do not need wrapping
20
20
  - All interface and method documentation must include **response data examples**
21
21
  - For simple list data, use Markdown tables to present information clearly and improve readability
22
- - Validate output Markdown for completeness, ensuring tables and d2 diagrams are properly formatted
23
- - **Content Integrity**: Generate complete, syntactically correct code blocks (d2, JSON, etc.). Perform self-validation to ensure all code blocks, lists, and tables are properly closed without truncation
24
- - **Code Block Atomicity**: Treat code blocks (e.g., ```d2 ... ```) as indivisible units. Generate them completely from opening marker (```d2) to closing marker (```) without interruption
22
+ - Validate output Markdown for completeness, ensuring tables are properly formatted
23
+ - **Content Integrity**: Generate complete, syntactically correct code blocks (JSON, etc.). Perform self-validation to ensure all code blocks, lists, and tables are properly closed without truncation
25
24
  - **Markdown Syntax Validation**: Ensure correct Markdown formatting, particularly table separators (e.g., `|---|---|---|`) that match column counts
26
25
  - Use README files for reference only—extract the most current and comprehensive information directly from source code
27
26
  - Omit tag information from document headers as it's processed programmatically
@@ -37,8 +37,14 @@ Custom component generation rules:
37
37
  Custom code block generation rules:
38
38
  {% include "custom/custom-code-block.md" %}
39
39
 
40
- D2 Diagram Generation Expert Guide:
41
- {% include "d2-chart/rules.md" %}
40
+ Diagram generate guide:
41
+ Evaluate for each document whether diagrams are necessary.
42
+ - Use diagrams to clarify complex concepts and diversify the presentation of the page.
43
+ - The document overview page must include an architecture diagram that illustrates the entire document structure.
44
+ - For the first page of each section, include a structural diagram of the current module when it adds clarity.
45
+ - For individual article pages, consider detailed flowcharts when the content or overall architecture warrants them.
46
+ - The number of diagrams is flexible, but aim for 0-3 diagrams as a practical range.
47
+
42
48
  </content_generation_rules>
43
49
 
44
50
  {% if glossary %}
@@ -70,8 +70,6 @@ Custom component optimization rules:
70
70
  Custom code block optimization rules:
71
71
  {% include "custom/custom-code-block.md" %}
72
72
 
73
- D2 Diagram optimization rules:
74
- {% include "d2-chart/rules.md" %}
75
73
  </content_optimization_rules>
76
74
 
77
75
  {% if glossary %}
@@ -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
- select_3: "domainFamiliar", // Reader knowledge level
990
- select_4: "balancedCoverage", // Documentation depth
991
- select_5: "en", // Primary language
992
- checkbox_6: ["zh", "ja"], // Translation languages
993
- input_7: join(tempDir, "docs"), // Documentation directory
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
- select_3: "experiencedUsers", // Reader knowledge level
1046
- select_4: "comprehensive", // Documentation depth
1047
- select_5: "zh-CN", // Primary language
1048
- checkbox_6: ["en"], // Translation languages
1049
- input_7: join(tempDir, "documentation"), // Documentation directory
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
- select_3: "completeBeginners", // Reader knowledge level
1091
- select_4: "essentialOnly", // Documentation depth
1092
- select_5: "en", // Primary language
1093
- checkbox_6: [], // No translation languages
1094
- input_7: join(tempDir, "simple-docs"), // Documentation directory
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/8]") && options.validate) {
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/8]")) {
1345
+ if (options.message.includes("[1/9]")) {
1340
1346
  return Promise.resolve(["getStarted"]); // Valid document purpose
1341
1347
  }
1342
- if (options.message.includes("[2/8]") && options.validate) {
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/8]")) {
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
@@ -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
 
@@ -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(), ".aigne", "doc-smith", ".tmp", "assets", "d2");
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, ".aigne", "doc-smith", ".tmp");
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, ".aigne", "doc-smith", ".tmp");
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
@@ -91,15 +91,8 @@ describe("deploy", () => {
91
91
 
92
92
  // Verify BrokerClient was constructed with correct config
93
93
  expect(mockBrokerClientConstructor).toHaveBeenCalledWith({
94
- baseUrl: "",
95
94
  authToken: "mock-auth-token",
96
- paymentLinkKey: "PAYMENT_LINK_ID",
97
- timeout: 300000,
98
- polling: {
99
- interval: 3000,
100
- maxAttempts: 100,
101
- backoffStrategy: "linear",
102
- },
95
+ baseUrl: "https://docsmith.aigne.io",
103
96
  });
104
97
 
105
98
  // Verify deploy was called with correct parameters
@@ -120,7 +113,6 @@ describe("deploy", () => {
120
113
  ACCESS_PREPARING: expect.any(Function),
121
114
  ACCESS_READY: expect.any(Function),
122
115
  }),
123
- onError: expect.any(Function),
124
116
  }),
125
117
  );
126
118
 
@@ -348,7 +340,8 @@ describe("deploy", () => {
348
340
  // Verify BrokerClient was constructed with empty baseUrl
349
341
  expect(mockBrokerClientConstructor).toHaveBeenCalledWith(
350
342
  expect.objectContaining({
351
- baseUrl: "",
343
+ authToken: "mock-auth-token",
344
+ baseUrl: "https://docsmith.aigne.io",
352
345
  }),
353
346
  );
354
347
  });
@@ -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
  checkD2Content,
@@ -451,7 +451,7 @@ E -> F
451
451
  try {
452
452
  await checkD2Content({ content });
453
453
 
454
- const assetDir = path.join(process.cwd(), ".aigne", "doc-smith", ".tmp", "assets", "d2");
454
+ const assetDir = path.join(process.cwd(), DOC_SMITH_DIR, TMP_DIR, TMP_ASSETS_DIR, "d2");
455
455
  const files = await readdir(assetDir);
456
456
  const d2File = files.find((file) => file.endsWith(".d2"));
457
457
  expect(d2File).toBeDefined();
@@ -470,7 +470,7 @@ E -> F
470
470
  try {
471
471
  await ensureTmpDir();
472
472
 
473
- const tmpDir = path.join(tempDir, ".aigne", "doc-smith", ".tmp");
473
+ const tmpDir = path.join(tempDir, DOC_SMITH_DIR, TMP_DIR);
474
474
  const gitignorePath = path.join(tmpDir, ".gitignore");
475
475
 
476
476
  expect(existsSync(tmpDir)).toBe(true);
@@ -491,7 +491,7 @@ E -> F
491
491
  // First call
492
492
  await ensureTmpDir();
493
493
 
494
- const tmpDir = path.join(tempDir, ".aigne", "doc-smith", ".tmp");
494
+ const tmpDir = path.join(tempDir, DOC_SMITH_DIR, TMP_DIR);
495
495
  const gitignorePath = path.join(tmpDir, ".gitignore");
496
496
 
497
497
  // Modify .gitignore to test if it gets overwritten
@@ -632,7 +632,7 @@ E -> F
632
632
  await Promise.all(promises);
633
633
 
634
634
  // Should only create directory once
635
- const tmpDir = path.join(tempDir, ".aigne", "doc-smith", ".tmp");
635
+ const tmpDir = path.join(tempDir, DOC_SMITH_DIR, TMP_DIR);
636
636
  expect(existsSync(tmpDir)).toBe(true);
637
637
 
638
638
  // .gitignore should be created properly
@@ -3,6 +3,8 @@ import { existsSync } from "node:fs";
3
3
  import { mkdir, rm, writeFile } from "node:fs/promises";
4
4
  import { dirname, join } from "node:path";
5
5
  import { fileURLToPath } from "node:url";
6
+
7
+ import { DOC_SMITH_DIR } from "../../utils/constants/index.mjs";
6
8
  import {
7
9
  addPreferenceRule,
8
10
  deactivateRule,
@@ -44,7 +46,7 @@ describe("preferences-utils", () => {
44
46
 
45
47
  test("should read existing preferences file", async () => {
46
48
  // Create preferences directory and file
47
- const prefsDir = join(testDir, ".aigne", "doc-smith");
49
+ const prefsDir = join(testDir, DOC_SMITH_DIR);
48
50
  await mkdir(prefsDir, { recursive: true });
49
51
 
50
52
  await writeFile(
@@ -69,7 +71,7 @@ describe("preferences-utils", () => {
69
71
 
70
72
  test("should handle malformed YAML gracefully", async () => {
71
73
  // Create preferences directory and invalid file
72
- const prefsDir = join(testDir, ".aigne", "doc-smith");
74
+ const prefsDir = join(testDir, DOC_SMITH_DIR);
73
75
  await mkdir(prefsDir, { recursive: true });
74
76
 
75
77
  await writeFile(join(prefsDir, "preferences.yml"), "invalid: yaml: content: [", "utf8");
@@ -85,7 +87,7 @@ describe("preferences-utils", () => {
85
87
 
86
88
  writePreferences(testPreferences);
87
89
 
88
- const prefsDir = join(testDir, ".aigne", "doc-smith");
90
+ const prefsDir = join(testDir, DOC_SMITH_DIR);
89
91
  expect(existsSync(prefsDir)).toBe(true);
90
92
  expect(existsSync(join(prefsDir, "preferences.yml"))).toBe(true);
91
93
  });
@@ -5,6 +5,8 @@ import fsPromisesDefault, * as fsPromises from "node:fs/promises";
5
5
  import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
6
6
  import { dirname, join } from "node:path";
7
7
  import { fileURLToPath } from "node:url";
8
+
9
+ import { DOC_SMITH_DIR } from "../../utils/constants/index.mjs";
8
10
  import { saveValueToConfig } from "../../utils/utils.mjs";
9
11
 
10
12
  const __filename = fileURLToPath(import.meta.url);
@@ -12,7 +14,7 @@ const __dirname = dirname(__filename);
12
14
 
13
15
  // Test directory for isolated testing
14
16
  const TEST_DIR = join(__dirname, "temp-config-test");
15
- const TEST_CONFIG_DIR = join(TEST_DIR, ".aigne", "doc-smith");
17
+ const TEST_CONFIG_DIR = join(TEST_DIR, DOC_SMITH_DIR);
16
18
  const TEST_CONFIG_PATH = join(TEST_CONFIG_DIR, "config.yaml");
17
19
 
18
20
  // Store original working directory
@@ -15,6 +15,7 @@ import {
15
15
  } from "./blocklet.mjs";
16
16
  import {
17
17
  BLOCKLET_ADD_COMPONENT_DOCS,
18
+ DEFAULT_APP_URL,
18
19
  DISCUSS_KIT_DID,
19
20
  DISCUSS_KIT_STORE_URL,
20
21
  DOC_OFFICIAL_ACCESS_TOKEN,
@@ -97,6 +98,9 @@ export async function getAccessToken(appUrl, ltToken = "") {
97
98
  appLogo: "https://docsmith.aigne.io/image-bin/uploads/9645caf64b4232699982c4d940b03b90.svg",
98
99
  openPage: (pageUrl) => {
99
100
  const url = new URL(pageUrl);
101
+ if (url.hostname !== DEFAULT_APP_URL) {
102
+ url.searchParams.set("required_roles", "owner,admin");
103
+ }
100
104
  if (ltToken) {
101
105
  url.searchParams.set("__lt", ltToken);
102
106
  }
@@ -334,6 +334,9 @@ export const PAYMENT_KIT_DID = "z2qaCNvKMv5GjouKdcDWexv6WqtHbpNPQDnAk";
334
334
 
335
335
  export const DOC_OFFICIAL_ACCESS_TOKEN = "DOC_OFFICIAL_ACCESS_TOKEN";
336
336
 
337
+ // Default application URL for the document deployment website.
338
+ export const DEFAULT_APP_URL = "https://docsmith.aigne.io";
339
+
337
340
  // Discuss Kit related URLs
338
341
  export const DISCUSS_KIT_STORE_URL =
339
342
  "https://store.blocklet.dev/blocklets/z8ia1WEiBZ7hxURf6LwH21Wpg99vophFwSJdu";
@@ -17,6 +17,8 @@ import { debug } from "./debug.mjs";
17
17
  import { iconMap } from "./icon-map.mjs";
18
18
  import { getContentHash } from "./utils.mjs";
19
19
 
20
+ const codeBlockRegex = /```d2.*\n([\s\S]*?)```/g;
21
+
20
22
  export async function getChart({ content, strict }) {
21
23
  const d2 = new D2();
22
24
  const iconUrlList = Object.keys(iconMap);
@@ -60,7 +62,7 @@ export async function getChart({ content, strict }) {
60
62
  } catch (err) {
61
63
  if (strict) throw err;
62
64
 
63
- console.error("Failed to generate D2 chart. Content:", content, "Error:", err);
65
+ console.error("Failed to generate D2 diagram. Content:", content, "Error:", err);
64
66
  return null;
65
67
  } finally {
66
68
  d2.worker.terminate();
@@ -73,8 +75,6 @@ export async function saveAssets({ markdown, docsDir }) {
73
75
  return markdown;
74
76
  }
75
77
 
76
- const codeBlockRegex = /```d2.*\n([\s\S]*?)```/g;
77
-
78
78
  const { replaced } = await runIterator({
79
79
  input: markdown,
80
80
  regexp: codeBlockRegex,
@@ -90,7 +90,7 @@ export async function saveAssets({ markdown, docsDir }) {
90
90
  debug("Found assets cache, skipping generation", svgPath);
91
91
  } else {
92
92
  try {
93
- debug("start generate d2 chart", svgPath);
93
+ debug("start generate d2 diagram", svgPath);
94
94
  if (debug.enabled) {
95
95
  const d2FileName = `${getContentHash(d2Content)}.d2`;
96
96
  const d2Path = path.join(assetDir, d2FileName);
@@ -102,7 +102,7 @@ export async function saveAssets({ markdown, docsDir }) {
102
102
  await fs.writeFile(svgPath, svg, { encoding: "utf8" });
103
103
  }
104
104
  } catch (error) {
105
- debug("Failed to generate D2 chart. Content:", d2Content, "Error:", error);
105
+ debug("Failed to generate D2 diagram. Content:", d2Content, "Error:", error);
106
106
  return _code;
107
107
  }
108
108
  }
@@ -156,7 +156,12 @@ async function runIterator({ input, regexp, fn = () => {}, options, replace = fa
156
156
  };
157
157
  }
158
158
 
159
- export async function checkContent({ content }) {
159
+ export async function checkContent({ content: _content }) {
160
+ const matches = Array.from(_content.matchAll(codeBlockRegex));
161
+ let content = _content;
162
+ if (matches.length > 0) {
163
+ content = matches[0][1];
164
+ }
160
165
  await ensureTmpDir();
161
166
  const assetDir = path.join(DOC_SMITH_DIR, TMP_DIR, TMP_ASSETS_DIR, "d2");
162
167
  await fs.ensureDir(assetDir);
package/utils/deploy.mjs CHANGED
@@ -2,10 +2,11 @@ import { BrokerClient, STEPS } from "@blocklet/payment-broker-client/node";
2
2
  import chalk from "chalk";
3
3
  import open from "open";
4
4
  import { getOfficialAccessToken } from "./auth-utils.mjs";
5
+ import { DEFAULT_APP_URL } from "./constants/index.mjs";
5
6
  import { saveValueToConfig } from "./utils.mjs";
6
7
 
7
8
  // ==================== Configuration ====================
8
- const BASE_URL = process.env.DOC_SMITH_BASE_URL || "";
9
+ const BASE_URL = process.env.DOC_SMITH_BASE_URL || DEFAULT_APP_URL;
9
10
  const SUCCESS_MESSAGE = {
10
11
  en: "Congratulations! Your website has been successfully installed. You can return to the command-line tool to continue the next steps.",
11
12
  zh: "恭喜您,你的网站已安装成功!可以返回命令行工具继续后续操作!",
@@ -24,26 +25,14 @@ export async function deploy(id, cachedUrl) {
24
25
  throw new Error("Failed to get official access token");
25
26
  }
26
27
 
27
- const client = new BrokerClient({
28
- baseUrl: BASE_URL,
29
- authToken,
30
- paymentLinkKey: "PAYMENT_LINK_ID",
31
- timeout: 300000,
32
- polling: {
33
- interval: 3000,
34
- maxAttempts: 100,
35
- backoffStrategy: "linear",
36
- },
37
- });
28
+ const client = new BrokerClient({ baseUrl: BASE_URL, authToken });
38
29
 
39
30
  console.log(`🚀 Starting deployment...`);
40
31
 
41
32
  const result = await client.deploy({
42
33
  cachedCheckoutId: id,
43
34
  cachedPaymentUrl: cachedUrl,
44
- pageInfo: {
45
- successMessage: SUCCESS_MESSAGE,
46
- },
35
+ pageInfo: { successMessage: SUCCESS_MESSAGE },
47
36
  hooks: {
48
37
  [STEPS.PAYMENT_PENDING]: async ({ sessionId, paymentUrl, isResuming }) => {
49
38
  console.log(`⏳ Step 1/4: Waiting for payment...`);
@@ -86,11 +75,6 @@ export async function deploy(id, cachedUrl) {
86
75
  }
87
76
  },
88
77
  },
89
-
90
- onError: (error, step) => {
91
- console.error(`${chalk.red("❌")} Deployment failed at ${step || "unknown step"}:`);
92
- console.error(` ${error.message}`);
93
- },
94
78
  });
95
79
 
96
80
  const { appUrl, homeUrl, subscriptionUrl, dashboardUrl, vendors } = result;
@@ -8,6 +8,7 @@ import { joinURL } from "ufo";
8
8
 
9
9
  import {
10
10
  D2_CONFIG,
11
+ DOC_SMITH_DIR,
11
12
  FILE_CONCURRENCY,
12
13
  KROKI_CONCURRENCY,
13
14
  TMP_ASSETS_DIR,
@@ -76,7 +77,7 @@ export async function saveD2Assets({ markdown, docsDir }) {
76
77
  debug("Found assets cache, skipping generation", svgPath);
77
78
  } else {
78
79
  try {
79
- debug("start generate d2 chart", svgPath);
80
+ debug("Start generate d2 diagram", svgPath);
80
81
  if (debug.enabled) {
81
82
  const d2FileName = `${getContentHash(d2Content)}.d2`;
82
83
  const d2Path = path.join(assetDir, d2FileName);
@@ -88,7 +89,7 @@ export async function saveD2Assets({ markdown, docsDir }) {
88
89
  await fs.writeFile(svgPath, svg, { encoding: "utf8" });
89
90
  }
90
91
  } catch (error) {
91
- debug("Failed to generate D2 chart:", error);
92
+ debug("Failed to generate D2 diagram:", error);
92
93
  return _code;
93
94
  }
94
95
  }
@@ -144,7 +145,7 @@ async function runIterator({ input, regexp, fn = () => {}, options, replace = fa
144
145
 
145
146
  export async function checkD2Content({ content }) {
146
147
  await ensureTmpDir();
147
- const assetDir = path.join(".aigne", "doc-smith", TMP_DIR, TMP_ASSETS_DIR, "d2");
148
+ const assetDir = path.join(DOC_SMITH_DIR, TMP_DIR, TMP_ASSETS_DIR, "d2");
148
149
  await fs.ensureDir(assetDir);
149
150
  const d2Content = [D2_CONFIG, content].join("\n");
150
151
  const fileName = `${getContentHash(d2Content)}.svg`;
@@ -166,7 +167,7 @@ export async function checkD2Content({ content }) {
166
167
  }
167
168
 
168
169
  export async function ensureTmpDir() {
169
- const tmpDir = path.join(".aigne", "doc-smith", TMP_DIR);
170
+ const tmpDir = path.join(DOC_SMITH_DIR, TMP_DIR);
170
171
  if (!(await fs.pathExists(path.join(tmpDir, ".gitignore")))) {
171
172
  await fs.ensureDir(tmpDir);
172
173
  await fs.writeFile(path.join(tmpDir, ".gitignore"), "**/*", { encoding: "utf8" });
@@ -1,14 +1,11 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
- import pMap from "p-map";
4
3
  import remarkGfm from "remark-gfm";
5
4
  import remarkLint from "remark-lint";
6
5
  import remarkParse from "remark-parse";
7
6
  import { unified } from "unified";
8
7
  import { visit } from "unist-util-visit";
9
8
  import { VFile } from "vfile";
10
- import { KROKI_CONCURRENCY } from "./constants/index.mjs";
11
- import { checkContent, isValidCode } from "./d2-utils.mjs";
12
9
  import { validateMermaidSyntax } from "./mermaid-validator.mjs";
13
10
 
14
11
  /**
@@ -378,7 +375,6 @@ export async function checkMarkdown(markdown, source = "content", options = {})
378
375
 
379
376
  // Check mermaid code blocks and other custom validations
380
377
  const mermaidChecks = [];
381
- const d2ChecksList = [];
382
378
  visit(ast, "code", (node) => {
383
379
  if (node.lang) {
384
380
  const line = node.position?.start?.line || "unknown";
@@ -467,13 +463,6 @@ export async function checkMarkdown(markdown, source = "content", options = {})
467
463
  specialCharMatch = nodeWithSpecialCharsRegex.exec(mermaidContent);
468
464
  }
469
465
  }
470
- if (isValidCode(node.lang)) {
471
- d2ChecksList.push({
472
- content: node.value,
473
- line,
474
- });
475
- }
476
- // TODO: @zhanghan need to check correctness of every code language
477
466
  }
478
467
  });
479
468
 
@@ -524,15 +513,6 @@ export async function checkMarkdown(markdown, source = "content", options = {})
524
513
 
525
514
  // Wait for all mermaid checks to complete
526
515
  await Promise.all(mermaidChecks);
527
- await pMap(
528
- d2ChecksList,
529
- async ({ content, line }) =>
530
- checkContent({ content }).catch((err) => {
531
- const errorMessage = err?.message || String(err) || "Unknown d2 syntax error";
532
- errorMessages.push(`Found D2 syntax error in ${source} at line ${line}: ${errorMessage}`);
533
- }),
534
- { concurrency: KROKI_CONCURRENCY },
535
- );
536
516
 
537
517
  // Run markdown linting rules
538
518
  await processor.run(ast, file);