@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.
- package/.aigne/doc-smith/config.yaml +1 -3
- package/.release-please-manifest.json +1 -1
- package/CHANGELOG.md +8 -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 +18 -10
- package/agents/publish/publish-docs.mjs +17 -20
- package/agents/update/generate-document.yaml +25 -0
- 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 +0 -2
- package/tests/agents/init/init.test.mjs +25 -19
- package/tests/agents/publish/publish-docs.test.mjs +99 -0
- package/tests/agents/utils/check-detail-result.test.mjs +2 -15
- 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/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/kroki-utils.mjs +5 -4
- package/utils/markdown-checker.mjs +0 -20
- /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
|
-
|
|
74
|
-
checkoutId: ""
|
|
72
|
+
appUrl: https://docsmith.aigne.io
|
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
|
+
|
package/agents/init/index.mjs
CHANGED
|
@@ -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/
|
|
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/
|
|
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.
|
|
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: "🧠 [
|
|
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: "📊 [
|
|
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: "🌐 [
|
|
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: "🔄 [
|
|
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: `📁 [
|
|
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🔍 [
|
|
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 {
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
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 (
|
|
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
|
-
|
|
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(
|
|
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.
|
|
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.
|
|
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
|
|
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>
|
|
@@ -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
|
|
23
|
-
- **Content Integrity**: Generate complete, syntactically correct code blocks (
|
|
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
|
-
|
|
41
|
-
|
|
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 %}
|
|
@@ -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
|
|
@@ -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(),
|
|
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
|
|
@@ -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
|
-
|
|
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
|
-
|
|
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(),
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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
|
package/utils/auth-utils.mjs
CHANGED
|
@@ -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";
|
package/utils/d2-utils.mjs
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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;
|
package/utils/kroki-utils.mjs
CHANGED
|
@@ -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("
|
|
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
|
|
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(
|
|
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(
|
|
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);
|
|
File without changes
|