Skip to content

Commit 781c01c

Browse files
committed
package.jsonにドキュメント生成スクリプトを追加
- `docs` スクリプトに `docs:json` と `docs:llms` を追加 - `generate-llms-txt.js` スクリプトを新規作成
1 parent a3aa28e commit 781c01c

2 files changed

Lines changed: 320 additions & 2 deletions

File tree

package.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,9 +59,11 @@
5959
"prepack": "run-z format build",
6060
"format": "eslint --fix src/** test/**",
6161
"test": "vitest run",
62-
"docs": "run-z docs:clean docs:typedoc",
62+
"docs": "run-z docs:clean docs:typedoc docs:json docs:llms",
6363
"docs:clean": "pnpm dlx rimraf docs",
64-
"docs:typedoc": "pnpm dlx typedoc --out ./docs src/index.ts"
64+
"docs:typedoc": "pnpm dlx typedoc --out ./docs src/index.ts",
65+
"docs:json": "pnpm dlx typedoc --json docs/api.json src/index.ts",
66+
"docs:llms": "node scripts/generate-llms-txt.js"
6567
},
6668
"devDependencies": {
6769
"@swc/core": "^1.11.29",

scripts/generate-llms-txt.js

Lines changed: 316 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,316 @@
1+
#!/usr/bin/env node
2+
3+
/**
4+
* Generate llms.txt for the Narou API wrapper library using TypeDoc JSON output
5+
* This script creates a comprehensive documentation file for LLMs
6+
*/
7+
8+
import fs from 'fs/promises';
9+
import path from 'path';
10+
import { fileURLToPath } from 'url';
11+
12+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
13+
const rootDir = path.join(__dirname, '..');
14+
15+
async function readFile(filePath) {
16+
try {
17+
return await fs.readFile(filePath, 'utf8');
18+
} catch (error) {
19+
console.warn(`Warning: Could not read ${filePath}: ${error.message}`);
20+
return null;
21+
}
22+
}
23+
24+
function formatComment(comment) {
25+
if (!comment || !comment.summary) return '';
26+
return comment.summary.map(part => part.text || '').join('');
27+
}
28+
29+
function formatType(type) {
30+
if (!type) return 'unknown';
31+
32+
switch (type.type) {
33+
case 'intrinsic':
34+
return type.name;
35+
case 'reference':
36+
return type.name;
37+
case 'array':
38+
return `${formatType(type.elementType)}[]`;
39+
case 'union':
40+
return type.types.map(formatType).join(' | ');
41+
case 'literal':
42+
return JSON.stringify(type.value);
43+
default:
44+
return type.name || 'unknown';
45+
}
46+
}
47+
48+
function formatParameters(parameters) {
49+
if (!parameters || parameters.length === 0) return '';
50+
51+
return parameters.map(param => {
52+
const optional = param.flags?.isOptional ? '?' : '';
53+
const type = formatType(param.type);
54+
const comment = formatComment(param.comment);
55+
return `- \`${param.name}${optional}: ${type}\` - ${comment}`;
56+
}).join('\n');
57+
}
58+
59+
function formatFunction(func) {
60+
const comment = formatComment(func.comment);
61+
const signatures = func.signatures || [];
62+
63+
let result = `### ${func.name}\n\n`;
64+
if (comment) {
65+
result += `${comment}\n\n`;
66+
}
67+
68+
signatures.forEach(sig => {
69+
const params = formatParameters(sig.parameters);
70+
const returnType = formatType(sig.type);
71+
72+
result += `**Signature:** \`${sig.name}(${sig.parameters?.map(p => `${p.name}${p.flags?.isOptional ? '?' : ''}: ${formatType(p.type)}`).join(', ') || ''}): ${returnType}\`\n\n`;
73+
74+
if (params) {
75+
result += `**Parameters:**\n${params}\n\n`;
76+
}
77+
});
78+
79+
return result;
80+
}
81+
82+
function formatClass(cls) {
83+
const comment = formatComment(cls.comment);
84+
let result = `## ${cls.name}\n\n`;
85+
86+
if (comment) {
87+
result += `${comment}\n\n`;
88+
}
89+
90+
const methods = cls.children?.filter(child => child.kind === 2048) || []; // Method kind
91+
const properties = cls.children?.filter(child => child.kind === 1024) || []; // Property kind
92+
93+
if (properties.length > 0) {
94+
result += `### Properties\n\n`;
95+
properties.forEach(prop => {
96+
const propComment = formatComment(prop.comment);
97+
const propType = formatType(prop.type);
98+
result += `- \`${prop.name}: ${propType}\` - ${propComment}\n`;
99+
});
100+
result += '\n';
101+
}
102+
103+
if (methods.length > 0) {
104+
result += `### Methods\n\n`;
105+
methods.forEach(method => {
106+
result += formatFunction(method);
107+
});
108+
}
109+
110+
return result;
111+
}
112+
113+
function formatInterface(iface) {
114+
const comment = formatComment(iface.comment);
115+
let result = `## ${iface.name}\n\n`;
116+
117+
if (comment) {
118+
result += `${comment}\n\n`;
119+
}
120+
121+
const properties = iface.children || [];
122+
if (properties.length > 0) {
123+
result += `### Properties\n\n`;
124+
properties.forEach(prop => {
125+
const propComment = formatComment(prop.comment);
126+
const propType = formatType(prop.type);
127+
const optional = prop.flags?.isOptional ? '?' : '';
128+
result += `- \`${prop.name}${optional}: ${propType}\` - ${propComment}\n`;
129+
});
130+
result += '\n';
131+
}
132+
133+
return result;
134+
}
135+
136+
async function processTypeDocJson(jsonPath) {
137+
const jsonContent = await readFile(jsonPath);
138+
if (!jsonContent) return '';
139+
140+
const apiDoc = JSON.parse(jsonContent);
141+
let result = '';
142+
143+
// Process main exports
144+
const children = apiDoc.children || [];
145+
146+
const classes = children.filter(child => child.kind === 128); // Class kind
147+
const interfaces = children.filter(child => child.kind === 256); // Interface kind
148+
const functions = children.filter(child => child.kind === 64); // Function kind
149+
const enums = children.filter(child => child.kind === 8); // Enum kind
150+
const types = children.filter(child => child.kind === 4194304); // Type alias kind
151+
152+
if (classes.length > 0) {
153+
result += `# Classes\n\n`;
154+
classes.forEach(cls => {
155+
result += formatClass(cls);
156+
});
157+
}
158+
159+
if (interfaces.length > 0) {
160+
result += `# Interfaces\n\n`;
161+
interfaces.forEach(iface => {
162+
result += formatInterface(iface);
163+
});
164+
}
165+
166+
if (functions.length > 0) {
167+
result += `# Functions\n\n`;
168+
functions.forEach(func => {
169+
result += formatFunction(func);
170+
});
171+
}
172+
173+
if (enums.length > 0) {
174+
result += `# Enums\n\n`;
175+
enums.forEach(enumItem => {
176+
const comment = formatComment(enumItem.comment);
177+
result += `## ${enumItem.name}\n\n`;
178+
if (comment) {
179+
result += `${comment}\n\n`;
180+
}
181+
182+
const members = enumItem.children || [];
183+
members.forEach(member => {
184+
const memberComment = formatComment(member.comment);
185+
result += `- \`${member.name}\` - ${memberComment}\n`;
186+
});
187+
result += '\n';
188+
});
189+
}
190+
191+
if (types.length > 0) {
192+
result += `# Type Aliases\n\n`;
193+
types.forEach(type => {
194+
const comment = formatComment(type.comment);
195+
const typeInfo = formatType(type.type);
196+
result += `## ${type.name}\n\n`;
197+
if (comment) {
198+
result += `${comment}\n\n`;
199+
}
200+
result += `\`\`\`typescript\ntype ${type.name} = ${typeInfo}\n\`\`\`\n\n`;
201+
});
202+
}
203+
204+
return result;
205+
}
206+
207+
async function generateLlmsTxt() {
208+
console.log('Generating llms.txt from TypeDoc JSON...');
209+
210+
const sections = [];
211+
212+
// Header
213+
sections.push(`# Narou API Wrapper - LLM Documentation
214+
215+
This is a TypeScript library that provides a fluent interface wrapper for the Narou (小説家になろう) developer APIs.
216+
It supports both Node.js (using fetch) and browser environments (using JSONP).
217+
218+
Generated on: ${new Date().toISOString()}
219+
220+
`);
221+
222+
// README
223+
const readme = await readFile(path.join(rootDir, 'README.md'));
224+
if (readme) {
225+
sections.push(`## README
226+
227+
${readme}
228+
229+
`);
230+
}
231+
232+
// Package.json for dependency information
233+
const packageJson = await readFile(path.join(rootDir, 'package.json'));
234+
if (packageJson) {
235+
sections.push(`## Package Information
236+
237+
\`\`\`json
238+
${packageJson}
239+
\`\`\`
240+
241+
`);
242+
}
243+
244+
// TypeDoc API Documentation
245+
const apiJsonPath = path.join(rootDir, 'docs', 'api.json');
246+
const apiDocumentation = await processTypeDocJson(apiJsonPath);
247+
if (apiDocumentation) {
248+
sections.push(`## API Documentation
249+
250+
${apiDocumentation}
251+
252+
`);
253+
}
254+
255+
// Supported APIs info
256+
sections.push(`## Supported APIs
257+
258+
This library wraps the following Narou developer APIs:
259+
260+
- [なろう小説 API](https://dev.syosetu.com/man/api/) - Novel search API
261+
- [なろう小説ランキング API](https://dev.syosetu.com/man/rankapi/) - Novel ranking API
262+
- [なろう殿堂入り API](https://dev.syosetu.com/man/rankinapi/) - Ranking history API
263+
- [なろう R18 小説 API](https://dev.syosetu.com/xman/api/) - R18 novel search API
264+
- [なろうユーザ検索 API](https://dev.syosetu.com/man/userapi/) - User search API
265+
266+
## Architecture
267+
268+
The library uses a builder pattern with fluent interfaces for API construction.
269+
It provides dual environment support through different entry points:
270+
271+
- \`src/index.ts\` - Node.js entry point using fetch
272+
- \`src/index.browser.ts\` - Browser entry point using JSONP
273+
274+
The core architecture includes:
275+
- Abstract base classes for builders
276+
- Environment-specific API implementations
277+
- Type-safe field selection with TypeScript generics
278+
- Comprehensive test coverage with MSW mocking
279+
280+
## Usage Examples
281+
282+
\`\`\`typescript
283+
import { search, ranking, rankingHistory, searchR18 } from "narou";
284+
285+
// Basic search
286+
const results = await search("異世界")
287+
.genre(Genre.RenaiIsekai)
288+
.order(Order.FavoriteNovelCount)
289+
.execute();
290+
291+
// Ranking
292+
const ranking = await ranking()
293+
.date(new Date())
294+
.type(RankingType.Daily)
295+
.execute();
296+
297+
// R18 search
298+
const r18Results = await searchR18("word")
299+
.r18Site(R18Site.Nocturne)
300+
.execute();
301+
\`\`\`
302+
303+
`);
304+
305+
const llmsTxt = sections.join('');
306+
307+
// Write to docs directory
308+
const docsDir = path.join(rootDir, 'docs');
309+
await fs.mkdir(docsDir, { recursive: true });
310+
await fs.writeFile(path.join(docsDir, 'llms.txt'), llmsTxt);
311+
312+
console.log('Generated llms.txt successfully!');
313+
console.log(`File size: ${(llmsTxt.length / 1024).toFixed(1)} KB`);
314+
}
315+
316+
generateLlmsTxt().catch(console.error);

0 commit comments

Comments
 (0)