@aigne/doc-smith 0.4.4 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,3 +1,4 @@
1
+ import { afterEach, beforeEach, describe, expect, test } from "bun:test";
1
2
  import { existsSync } from "node:fs";
2
3
  import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
3
4
  import { dirname, join } from "node:path";
@@ -44,221 +45,151 @@ async function createInitialConfig(content) {
44
45
  await writeFile(TEST_CONFIG_PATH, content, "utf8");
45
46
  }
46
47
 
47
- // Test utilities
48
- function assertContains(content, expected, message) {
49
- if (!content.includes(expected)) {
50
- throw new Error(
51
- `${message}\nExpected content to contain: ${expected}\nActual content:\n${content}`,
52
- );
53
- }
54
- }
48
+ // Test suite
49
+ describe("saveValueToConfig", () => {
50
+ beforeEach(async () => {
51
+ await setupTestDir();
52
+ });
55
53
 
56
- function assertNotContains(content, unexpected, message) {
57
- if (content.includes(unexpected)) {
58
- throw new Error(
59
- `${message}\nExpected content NOT to contain: ${unexpected}\nActual content:\n${content}`,
60
- );
61
- }
62
- }
54
+ afterEach(async () => {
55
+ await teardownTestDir();
56
+ });
63
57
 
64
- function assertEqual(actual, expected, message) {
65
- if (actual !== expected) {
66
- throw new Error(`${message}\nExpected: ${expected}\nActual: ${actual}`);
67
- }
68
- }
58
+ test("Save string value to empty file", async () => {
59
+ await saveValueToConfig("projectName", "test-project");
60
+ const content = await readConfigFile();
61
+
62
+ expect(content).toContain('projectName: "test-project"');
63
+ });
64
+
65
+ test("Save string value with comment to empty file", async () => {
66
+ await saveValueToConfig("projectDesc", "A test project", "Project description");
67
+ const content = await readConfigFile();
68
+
69
+ expect(content).toContain("# Project description");
70
+ expect(content).toContain('projectDesc: "A test project"');
71
+ });
72
+
73
+ test("Save array value to empty file", async () => {
74
+ await saveValueToConfig("translateLanguages", ["zh", "ja", "ko"]);
75
+ const content = await readConfigFile();
76
+
77
+ expect(content).toContain("translateLanguages:");
78
+ expect(content).toContain(" - zh");
79
+ expect(content).toContain(" - ja");
80
+ expect(content).toContain(" - ko");
81
+ });
82
+
83
+ test("Save empty array", async () => {
84
+ await saveValueToConfig("emptyArray", []);
85
+ const content = await readConfigFile();
86
+
87
+ expect(content).toContain("emptyArray: []");
88
+ });
89
+
90
+ test("Save array with comment", async () => {
91
+ await saveValueToConfig("sourcePaths", ["./src", "./lib"], "Source code paths");
92
+ const content = await readConfigFile();
93
+
94
+ expect(content).toContain("# Source code paths");
95
+ expect(content).toContain("sourcePaths:");
96
+ expect(content).toContain(" - ./src");
97
+ expect(content).toContain(" - ./lib");
98
+ });
99
+
100
+ test("Update existing string value", async () => {
101
+ await createInitialConfig('projectName: "old-project"\nversion: "1.0.0"');
102
+
103
+ await saveValueToConfig("projectName", "new-project");
104
+ const content = await readConfigFile();
105
+
106
+ expect(content).toContain('projectName: "new-project"');
107
+ expect(content).toContain('version: "1.0.0"');
108
+ expect(content).not.toContain("old-project");
109
+ });
69
110
 
70
- // Test cases
71
- const tests = [
72
- // Test 1: Save string value to empty file
73
- {
74
- name: "Save string value to empty file",
75
- async run() {
76
- await saveValueToConfig("projectName", "test-project");
77
- const content = await readConfigFile();
78
-
79
- assertContains(
80
- content,
81
- 'projectName: "test-project"',
82
- "Should save string value with quotes",
83
- );
84
- },
85
- },
86
-
87
- // Test 2: Save string value with comment to empty file
88
- {
89
- name: "Save string value with comment to empty file",
90
- async run() {
91
- await saveValueToConfig("projectDesc", "A test project", "Project description");
92
- const content = await readConfigFile();
93
-
94
- assertContains(content, "# Project description", "Should include comment");
95
- assertContains(content, 'projectDesc: "A test project"', "Should save string value");
96
- },
97
- },
98
-
99
- // Test 3: Save array value to empty file
100
- {
101
- name: "Save array value to empty file",
102
- async run() {
103
- await saveValueToConfig("translateLanguages", ["zh", "ja", "ko"]);
104
- const content = await readConfigFile();
105
-
106
- assertContains(content, "translateLanguages:", "Should have array key");
107
- assertContains(content, " - zh", "Should have first array item");
108
- assertContains(content, " - ja", "Should have second array item");
109
- assertContains(content, " - ko", "Should have third array item");
110
- },
111
- },
112
-
113
- // Test 4: Save empty array
114
- {
115
- name: "Save empty array",
116
- async run() {
117
- await saveValueToConfig("emptyArray", []);
118
- const content = await readConfigFile();
119
-
120
- assertContains(content, "emptyArray: []", "Should save empty array as []");
121
- },
122
- },
123
-
124
- // Test 5: Save array with comment
125
- {
126
- name: "Save array with comment",
127
- async run() {
128
- await saveValueToConfig("sourcePaths", ["./src", "./lib"], "Source code paths");
129
- const content = await readConfigFile();
130
-
131
- assertContains(content, "# Source code paths", "Should include comment");
132
- assertContains(content, "sourcePaths:", "Should have array key");
133
- assertContains(content, " - ./src", "Should have first path");
134
- assertContains(content, " - ./lib", "Should have second path");
135
- },
136
- },
137
-
138
- // Test 6: Update existing string value
139
- {
140
- name: "Update existing string value",
141
- async run() {
142
- await createInitialConfig('projectName: "old-project"\nversion: "1.0.0"');
143
-
144
- await saveValueToConfig("projectName", "new-project");
145
- const content = await readConfigFile();
146
-
147
- assertContains(content, 'projectName: "new-project"', "Should update string value");
148
- assertContains(content, 'version: "1.0.0"', "Should preserve other values");
149
- assertNotContains(content, "old-project", "Should not contain old value");
150
- },
151
- },
152
-
153
- // Test 7: Update existing array value
154
- {
155
- name: "Update existing array value",
156
- async run() {
157
- await createInitialConfig(`translateLanguages:
111
+ test("Update existing array value", async () => {
112
+ await createInitialConfig(`translateLanguages:
158
113
  - en
159
114
  - fr
160
115
  version: "1.0.0"`);
161
116
 
162
- await saveValueToConfig("translateLanguages", ["zh", "ja"]);
163
- const content = await readConfigFile();
164
-
165
- assertContains(content, "translateLanguages:", "Should have array key");
166
- assertContains(content, " - zh", "Should have new first item");
167
- assertContains(content, " - ja", "Should have new second item");
168
- assertNotContains(content, " - en", "Should not contain old first item");
169
- assertNotContains(content, " - fr", "Should not contain old second item");
170
- assertContains(content, 'version: "1.0.0"', "Should preserve other values");
171
- },
172
- },
173
-
174
- // Test 8: Update array to empty array
175
- {
176
- name: "Update array to empty array",
177
- async run() {
178
- await createInitialConfig(`translateLanguages:
117
+ await saveValueToConfig("translateLanguages", ["zh", "ja"]);
118
+ const content = await readConfigFile();
119
+
120
+ expect(content).toContain("translateLanguages:");
121
+ expect(content).toContain(" - zh");
122
+ expect(content).toContain(" - ja");
123
+ expect(content).not.toContain(" - en");
124
+ expect(content).not.toContain(" - fr");
125
+ expect(content).toContain('version: "1.0.0"');
126
+ });
127
+
128
+ test("Update array to empty array", async () => {
129
+ await createInitialConfig(`translateLanguages:
179
130
  - en
180
131
  - fr
181
132
  version: "1.0.0"`);
182
133
 
183
- await saveValueToConfig("translateLanguages", []);
184
- const content = await readConfigFile();
134
+ await saveValueToConfig("translateLanguages", []);
135
+ const content = await readConfigFile();
185
136
 
186
- assertContains(content, "translateLanguages: []", "Should update to empty array");
187
- assertNotContains(content, " - en", "Should remove old items");
188
- assertContains(content, 'version: "1.0.0"', "Should preserve other values");
189
- },
190
- },
137
+ expect(content).toContain("translateLanguages: []");
138
+ expect(content).not.toContain(" - en");
139
+ expect(content).toContain('version: "1.0.0"');
140
+ });
191
141
 
192
- // Test 9: Update empty array to populated array
193
- {
194
- name: "Update empty array to populated array",
195
- async run() {
196
- await createInitialConfig(`translateLanguages: []
142
+ test("Update empty array to populated array", async () => {
143
+ await createInitialConfig(`translateLanguages: []
197
144
  version: "1.0.0"`);
198
145
 
199
- await saveValueToConfig("translateLanguages", ["zh", "ja"]);
200
- const content = await readConfigFile();
201
-
202
- assertContains(content, "translateLanguages:", "Should have array key");
203
- assertContains(content, " - zh", "Should have first item");
204
- assertContains(content, " - ja", "Should have second item");
205
- assertNotContains(content, "translateLanguages: []", "Should not contain empty array format");
206
- assertContains(content, 'version: "1.0.0"', "Should preserve other values");
207
- },
208
- },
209
-
210
- // Test 10: Add comment to existing value without comment
211
- {
212
- name: "Add comment to existing value without comment",
213
- async run() {
214
- await createInitialConfig('projectName: "test-project"\nversion: "1.0.0"');
215
-
216
- await saveValueToConfig("projectName", "updated-project", "Updated project name");
217
- const content = await readConfigFile();
218
-
219
- assertContains(content, "# Updated project name", "Should add comment");
220
- assertContains(content, 'projectName: "updated-project"', "Should update value");
221
- },
222
- },
223
-
224
- // Test 11: Update value that already has comment
225
- {
226
- name: "Update value that already has comment",
227
- async run() {
228
- await createInitialConfig(`# Project information
146
+ await saveValueToConfig("translateLanguages", ["zh", "ja"]);
147
+ const content = await readConfigFile();
148
+
149
+ expect(content).toContain("translateLanguages:");
150
+ expect(content).toContain(" - zh");
151
+ expect(content).toContain(" - ja");
152
+ expect(content).not.toContain("translateLanguages: []");
153
+ expect(content).toContain('version: "1.0.0"');
154
+ });
155
+
156
+ test("Add comment to existing value without comment", async () => {
157
+ await createInitialConfig('projectName: "test-project"\nversion: "1.0.0"');
158
+
159
+ await saveValueToConfig("projectName", "updated-project", "Updated project name");
160
+ const content = await readConfigFile();
161
+
162
+ expect(content).toContain("# Updated project name");
163
+ expect(content).toContain('projectName: "updated-project"');
164
+ });
165
+
166
+ test("Update value that already has comment", async () => {
167
+ await createInitialConfig(`# Project information
229
168
  projectName: "old-project"
230
169
  version: "1.0.0"`);
231
170
 
232
- await saveValueToConfig("projectName", "new-project", "Updated project info");
233
- const content = await readConfigFile();
234
-
235
- assertContains(content, "# Project information", "Should preserve existing comment");
236
- assertContains(content, 'projectName: "new-project"', "Should update value");
237
- assertNotContains(content, "# Updated project info", "Should not add duplicate comment");
238
- },
239
- },
240
-
241
- // Test 12: Complex array with various items
242
- {
243
- name: "Complex array with various items",
244
- async run() {
245
- const complexArray = ["./src/components", "./lib/utils", "./modules/auth"];
246
- await saveValueToConfig("sourcePaths", complexArray, "All source paths");
247
- const content = await readConfigFile();
248
-
249
- assertContains(content, "# All source paths", "Should include comment");
250
- assertContains(content, "sourcePaths:", "Should have array key");
251
- assertContains(content, " - ./src/components", "Should have first complex path");
252
- assertContains(content, " - ./lib/utils", "Should have second complex path");
253
- assertContains(content, " - ./modules/auth", "Should have third complex path");
254
- },
255
- },
256
-
257
- // Test 13: Mixed content preservation
258
- {
259
- name: "Mixed content preservation",
260
- async run() {
261
- await createInitialConfig(`# Project configuration
171
+ await saveValueToConfig("projectName", "new-project", "Updated project info");
172
+ const content = await readConfigFile();
173
+
174
+ expect(content).toContain("# Project information");
175
+ expect(content).toContain('projectName: "new-project"');
176
+ expect(content).not.toContain("# Updated project info");
177
+ });
178
+
179
+ test("Complex array with various items", async () => {
180
+ const complexArray = ["./src/components", "./lib/utils", "./modules/auth"];
181
+ await saveValueToConfig("sourcePaths", complexArray, "All source paths");
182
+ const content = await readConfigFile();
183
+
184
+ expect(content).toContain("# All source paths");
185
+ expect(content).toContain("sourcePaths:");
186
+ expect(content).toContain(" - ./src/components");
187
+ expect(content).toContain(" - ./lib/utils");
188
+ expect(content).toContain(" - ./modules/auth");
189
+ });
190
+
191
+ test("Mixed content preservation", async () => {
192
+ await createInitialConfig(`# Project configuration
262
193
  projectName: "original-project"
263
194
  translateLanguages:
264
195
  - en
@@ -267,104 +198,50 @@ translateLanguages:
267
198
  version: "1.0.0"
268
199
  locale: en`);
269
200
 
270
- await saveValueToConfig("translateLanguages", ["zh", "ja", "ko"], "Updated languages");
271
- const content = await readConfigFile();
272
-
273
- assertContains(content, "# Project configuration", "Should preserve file header comment");
274
- assertContains(content, 'projectName: "original-project"', "Should preserve string value");
275
- assertContains(content, "# Updated languages", "Should add new comment");
276
- assertContains(content, "translateLanguages:", "Should have updated array key");
277
- assertContains(content, " - zh", "Should have new array items");
278
- assertContains(content, " - ja", "Should have new array items");
279
- assertContains(content, " - ko", "Should have new array items");
280
- assertContains(content, "# Other settings", "Should preserve other comments");
281
- assertContains(content, 'version: "1.0.0"', "Should preserve other values");
282
- assertContains(content, "locale: en", "Should preserve other values");
283
- assertNotContains(content, " - en", "Should remove old array items");
284
- assertNotContains(content, " - fr", "Should remove old array items");
285
- },
286
- },
287
-
288
- // Test 14: Array in middle of file
289
- {
290
- name: "Array in middle of file",
291
- async run() {
292
- await createInitialConfig(`projectName: "test-project"
201
+ await saveValueToConfig("translateLanguages", ["zh", "ja", "ko"], "Updated languages");
202
+ const content = await readConfigFile();
203
+
204
+ expect(content).toContain("# Project configuration");
205
+ expect(content).toContain('projectName: "original-project"');
206
+ expect(content).toContain("# Updated languages");
207
+ expect(content).toContain("translateLanguages:");
208
+ expect(content).toContain(" - zh");
209
+ expect(content).toContain(" - ja");
210
+ expect(content).toContain(" - ko");
211
+ expect(content).toContain("# Other settings");
212
+ expect(content).toContain('version: "1.0.0"');
213
+ expect(content).toContain("locale: en");
214
+ expect(content).not.toContain(" - en");
215
+ expect(content).not.toContain(" - fr");
216
+ });
217
+
218
+ test("Array in middle of file", async () => {
219
+ await createInitialConfig(`projectName: "test-project"
293
220
  translateLanguages:
294
221
  - en
295
222
  - fr
296
223
  locale: en
297
224
  version: "1.0.0"`);
298
225
 
299
- await saveValueToConfig("translateLanguages", ["zh"]);
300
- const content = await readConfigFile();
301
-
302
- assertContains(content, 'projectName: "test-project"', "Should preserve content before");
303
- assertContains(content, "translateLanguages:", "Should have array key");
304
- assertContains(content, " - zh", "Should have new array item");
305
- assertContains(content, "locale: en", "Should preserve content after");
306
- assertContains(content, 'version: "1.0.0"', "Should preserve content after");
307
- assertNotContains(content, " - en", "Should remove old items");
308
- assertNotContains(content, " - fr", "Should remove old items");
309
- },
310
- },
311
-
312
- // Test 15: Handle undefined values
313
- {
314
- name: "Handle undefined values",
315
- async run() {
316
- const initialContent = 'projectName: "test-project"';
317
- await createInitialConfig(initialContent);
318
-
319
- await saveValueToConfig("undefinedKey", undefined);
320
- const content = await readConfigFile();
321
-
322
- assertEqual(content, initialContent, "Should not modify file when value is undefined");
323
- },
324
- },
325
- ];
326
-
327
- // Run all tests
328
- async function runTests() {
329
- console.log("🧪 Running saveValueToConfig tests...\n");
330
-
331
- let passed = 0;
332
- let failed = 0;
333
-
334
- for (const test of tests) {
335
- try {
336
- await setupTestDir();
337
- await test.run();
338
- console.log(`✅ ${test.name}`);
339
- passed++;
340
- } catch (error) {
341
- console.log(`❌ ${test.name}`);
342
- console.log(` Error: ${error.message}\n`);
343
- failed++;
344
- } finally {
345
- await teardownTestDir();
346
- }
347
- }
226
+ await saveValueToConfig("translateLanguages", ["zh"]);
227
+ const content = await readConfigFile();
348
228
 
349
- console.log(`\n📊 Test Results:`);
350
- console.log(` Passed: ${passed}`);
351
- console.log(` Failed: ${failed}`);
352
- console.log(` Total: ${tests.length}`);
229
+ expect(content).toContain('projectName: "test-project"');
230
+ expect(content).toContain("translateLanguages:");
231
+ expect(content).toContain(" - zh");
232
+ expect(content).toContain("locale: en");
233
+ expect(content).toContain('version: "1.0.0"');
234
+ expect(content).not.toContain(" - en");
235
+ expect(content).not.toContain(" - fr");
236
+ });
353
237
 
354
- if (failed > 0) {
355
- console.log(`\n❌ ${failed} test(s) failed`);
356
- process.exit(1);
357
- } else {
358
- console.log(`\n🎉 All tests passed!`);
359
- }
360
- }
238
+ test("Handle undefined values", async () => {
239
+ const initialContent = 'projectName: "test-project"';
240
+ await createInitialConfig(initialContent);
361
241
 
362
- // Run tests if this file is executed directly
363
- if (import.meta.url === `file://${process.argv[1]}`) {
364
- runTests().catch((error) => {
365
- console.error("Test runner failed:", error);
366
- process.exit(1);
367
- });
368
- }
242
+ await saveValueToConfig("undefinedKey", undefined);
243
+ const content = await readConfigFile();
369
244
 
370
- export { runTests, tests };
245
+ expect(content).toBe(initialContent);
246
+ });
247
+ });
@@ -176,13 +176,13 @@ export const DOCUMENT_STYLES = {
176
176
  "Optimizes for: Architecture, concepts, decision rationale.\nSkips: Quick wins, copy-paste solutions.",
177
177
  },
178
178
  solveProblems: {
179
- name: "Solve problems",
179
+ name: "Troubleshoot common issues",
180
180
  description: "Help users troubleshoot and fix issues",
181
181
  content:
182
182
  "Optimizes for: Error scenarios, diagnostics, solutions.\nSkips: Happy path tutorials, feature overviews.",
183
183
  },
184
184
  mixedPurpose: {
185
- name: "Mix of above",
185
+ name: "Serve multiple purposes",
186
186
  description: "Comprehensive documentation covering multiple needs",
187
187
  content:
188
188
  "Optimizes for: Balanced coverage, multiple entry points.\nTrade-off: Longer, requires good navigation.",
@@ -198,13 +198,13 @@ export const TARGET_AUDIENCES = {
198
198
  "Writing: Plain language, UI-focused, avoid technical terms.\nExamples: Screenshots, step-by-step clicks, business outcomes.",
199
199
  },
200
200
  developers: {
201
- name: "Developers integrating",
201
+ name: "Developers integrating your product/API",
202
202
  description: "Engineers adding this to their projects",
203
203
  content:
204
204
  "Writing: Code-first, copy-paste ready, technical accuracy.\nExamples: SDK usage, API calls, configuration snippets.",
205
205
  },
206
206
  devops: {
207
- name: "DevOps/Infrastructure",
207
+ name: "DevOps / SRE / Infrastructure teams",
208
208
  description: "Teams deploying, monitoring, maintaining systems",
209
209
  content:
210
210
  "Writing: Operations-focused, troubleshooting emphasis.\nExamples: Deployment configs, monitoring setup, scaling guides.",
@@ -232,19 +232,19 @@ export const TARGET_AUDIENCES = {
232
232
  // Reader knowledge level - what do readers typically know when they arrive?
233
233
  export const READER_KNOWLEDGE_LEVELS = {
234
234
  completeBeginners: {
235
- name: "Complete beginners",
235
+ name: "Is a total beginner, starting from scratch",
236
236
  description: "New to this domain/technology entirely",
237
237
  content:
238
238
  "Content: Define terms, explain concepts, assume nothing.\nStructure: Linear progression, prerequisite checks.\nTone: Patient, educational, confidence-building.",
239
239
  },
240
240
  domainFamiliar: {
241
- name: "Domain-familiar, tool-new",
241
+ name: "Has used similar tools before",
242
242
  description: "Know the problem space, new to this specific solution",
243
243
  content:
244
244
  'Content: Focus on differences, migration, unique features.\nStructure: Comparison-heavy, "coming from X" sections.\nTone: Efficient, highlight advantages, skip basics.',
245
245
  },
246
246
  experiencedUsers: {
247
- name: "Experienced users",
247
+ name: "Is an expert trying to do something specific",
248
248
  description: "Regular users needing reference/advanced topics",
249
249
  content:
250
250
  "Content: Dense information, edge cases, performance tips.\nStructure: Reference-style, searchable, task-oriented.\nTone: Concise, technical precision, assume competence.",
@@ -256,7 +256,7 @@ export const READER_KNOWLEDGE_LEVELS = {
256
256
  "Content: Symptom → diagnosis → solution format.\nStructure: Problem-first, solution-prominent, escalation paths.\nTone: Clear, actionable, confidence-inspiring.",
257
257
  },
258
258
  exploringEvaluating: {
259
- name: "Exploring/evaluating",
259
+ name: "Is evaluating this tool against others",
260
260
  description: "Trying to understand if this fits their needs",
261
261
  content:
262
262
  "Content: Use cases, comparisons, getting started quickly.\nStructure: Multiple entry points, progressive commitment.\nTone: Persuasive but honest, show value quickly.",