@aliaksei-raketski/pi-taiga-ui-docs 0.1.0
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/LICENSE +190 -0
- package/README.md +58 -0
- package/package.json +37 -0
- package/skills/taiga-ui-docs/SKILL.md +53 -0
- package/skills/taiga-ui-docs/scripts/taiga-ui-docs.mjs +1719 -0
|
@@ -0,0 +1,1719 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// Copyright 2024 Acpekt
|
|
4
|
+
//
|
|
5
|
+
// Licensed under the Apache License, Version 2.0.
|
|
6
|
+
// This file reuses and ports logic from @taiga-ui/mcp (Apache-2.0).
|
|
7
|
+
|
|
8
|
+
import fs from 'node:fs/promises';
|
|
9
|
+
import os from 'node:os';
|
|
10
|
+
import path from 'node:path';
|
|
11
|
+
|
|
12
|
+
const DEFAULT_SOURCE_URL = 'https://taiga-ui.dev/llms-full.txt';
|
|
13
|
+
const DEFAULT_TTL_MS = 6 * 60 * 60 * 1000; // 6h
|
|
14
|
+
const DEFAULT_CACHE_DIR = path.join(os.homedir(), '.cache', 'taiga-ui-docs');
|
|
15
|
+
const COMMANDS = ['overview', 'list', 'example', 'migration'];
|
|
16
|
+
const CACHE_SOURCE_FILE = 'source.txt';
|
|
17
|
+
const CACHE_META_FILE = 'meta.json';
|
|
18
|
+
|
|
19
|
+
const GENERIC_SUFFIXES = new Set([
|
|
20
|
+
'component',
|
|
21
|
+
'context',
|
|
22
|
+
'directive',
|
|
23
|
+
'guard',
|
|
24
|
+
'interceptor',
|
|
25
|
+
'module',
|
|
26
|
+
'options',
|
|
27
|
+
'pipe',
|
|
28
|
+
'service',
|
|
29
|
+
]);
|
|
30
|
+
|
|
31
|
+
const OPTION_DEFINITIONS = {
|
|
32
|
+
'source-url': {key: 'sourceUrl', expectsValue: true},
|
|
33
|
+
'source-file': {key: 'sourceFile', expectsValue: true},
|
|
34
|
+
'cache-dir': {key: 'cacheDir', expectsValue: true},
|
|
35
|
+
'refresh': {key: 'refresh', expectsValue: false},
|
|
36
|
+
'no-cache': {key: 'noCache', expectsValue: false},
|
|
37
|
+
'ttl-ms': {key: 'ttlMs', expectsValue: true},
|
|
38
|
+
'limit': {key: 'limit', expectsValue: true},
|
|
39
|
+
'offset': {key: 'offset', expectsValue: true},
|
|
40
|
+
'max-chars': {key: 'maxChars', expectsValue: true},
|
|
41
|
+
'output': {key: 'output', expectsValue: true},
|
|
42
|
+
'force': {key: 'force', expectsValue: false},
|
|
43
|
+
'pretty': {key: 'pretty', expectsValue: false},
|
|
44
|
+
'help': {key: 'help', expectsValue: false},
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
async function main() {
|
|
48
|
+
const parsed = parseCommandLine(process.argv);
|
|
49
|
+
|
|
50
|
+
if (parsed.errors.length) {
|
|
51
|
+
await failWithCode(2, {error: parsed.errors.join('\n')}, parsed.options, {
|
|
52
|
+
helpHint: true,
|
|
53
|
+
});
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const resolved = normalizeOptions(parsed);
|
|
58
|
+
|
|
59
|
+
if (resolved.errors.length) {
|
|
60
|
+
await failWithCode(2, {error: resolved.errors.join('\n')}, resolved.options, {
|
|
61
|
+
helpHint: true,
|
|
62
|
+
});
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (resolved.options.help) {
|
|
67
|
+
printHelp();
|
|
68
|
+
process.exit(0);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const command = resolved.command;
|
|
72
|
+
|
|
73
|
+
if (!command) {
|
|
74
|
+
await failWithCode(2, {error: 'Missing command.'}, resolved.options, {
|
|
75
|
+
helpHint: true,
|
|
76
|
+
});
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (!COMMANDS.includes(command)) {
|
|
81
|
+
console.error(`Error: command "${command}" is not supported.`);
|
|
82
|
+
console.error(`Expected one of: ${COMMANDS.join(', ')}.`);
|
|
83
|
+
console.error('Try: node scripts/taiga-ui-docs.mjs --help');
|
|
84
|
+
process.exit(2);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
let sourceDescriptor;
|
|
88
|
+
|
|
89
|
+
try {
|
|
90
|
+
const sourceConfig = resolveSourceConfig(resolved.options);
|
|
91
|
+
sourceDescriptor = await loadSource(sourceConfig, resolved.options);
|
|
92
|
+
} catch (error) {
|
|
93
|
+
await failWithCode(
|
|
94
|
+
3,
|
|
95
|
+
{error: error instanceof Error ? error.message : String(error)},
|
|
96
|
+
resolved.options,
|
|
97
|
+
{diagnostic: error instanceof Error ? error.message : String(error)},
|
|
98
|
+
);
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
let index;
|
|
103
|
+
|
|
104
|
+
try {
|
|
105
|
+
index = parseContent(sourceDescriptor.content, sourceDescriptor.source);
|
|
106
|
+
} catch (error) {
|
|
107
|
+
await failWithCode(
|
|
108
|
+
3,
|
|
109
|
+
{error: error instanceof Error ? error.message : String(error)},
|
|
110
|
+
resolved.options,
|
|
111
|
+
{diagnostic: error instanceof Error ? error.message : String(error)},
|
|
112
|
+
);
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (command === 'overview') {
|
|
117
|
+
const payload = handleOverview(index);
|
|
118
|
+
await emitResult(payload, resolved.options);
|
|
119
|
+
process.exit(0);
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (command === 'list') {
|
|
124
|
+
const query = resolved.commandArgs.join(' ');
|
|
125
|
+
const payload = handleList(index, query, resolved.options);
|
|
126
|
+
await emitResult(payload, resolved.options);
|
|
127
|
+
process.exit(0);
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (command === 'example') {
|
|
132
|
+
const names = resolved.commandArgs;
|
|
133
|
+
const validation = validateExampleNames(names);
|
|
134
|
+
|
|
135
|
+
if (!validation.ok) {
|
|
136
|
+
await failWithCode(
|
|
137
|
+
2,
|
|
138
|
+
{error: validation.error},
|
|
139
|
+
resolved.options,
|
|
140
|
+
{helpHint: false, diagnostic: validation.error},
|
|
141
|
+
);
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const payload = handleExample(index, names, resolved.options);
|
|
146
|
+
await emitResult(payload, resolved.options);
|
|
147
|
+
process.exit(0);
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (command === 'migration') {
|
|
152
|
+
const payload = handleMigration(index);
|
|
153
|
+
const exitCode = payload.error ? 4 : 0;
|
|
154
|
+
await emitResult(payload, resolved.options);
|
|
155
|
+
process.exit(exitCode);
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function parseCommandLine(argv) {
|
|
161
|
+
const errors = [];
|
|
162
|
+
const options = {};
|
|
163
|
+
let command = null;
|
|
164
|
+
const commandArgs = [];
|
|
165
|
+
|
|
166
|
+
for (let index = 2; index < argv.length; index++) {
|
|
167
|
+
const arg = argv[index];
|
|
168
|
+
|
|
169
|
+
if (!arg.startsWith('--')) {
|
|
170
|
+
if (command === null) {
|
|
171
|
+
command = arg;
|
|
172
|
+
} else {
|
|
173
|
+
commandArgs.push(arg);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const raw = arg.slice(2);
|
|
180
|
+
const eqIndex = raw.indexOf('=');
|
|
181
|
+
const name = eqIndex === -1 ? raw : raw.slice(0, eqIndex);
|
|
182
|
+
const value = eqIndex === -1 ? undefined : raw.slice(eqIndex + 1);
|
|
183
|
+
|
|
184
|
+
const definition = OPTION_DEFINITIONS[name];
|
|
185
|
+
|
|
186
|
+
if (!definition) {
|
|
187
|
+
errors.push(`Unknown option: --${name}`);
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (definition.expectsValue) {
|
|
192
|
+
if (value === undefined) {
|
|
193
|
+
const next = argv[index + 1];
|
|
194
|
+
|
|
195
|
+
if (!next || next.startsWith('--')) {
|
|
196
|
+
errors.push(`Option --${name} requires a value.`);
|
|
197
|
+
continue;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
options[definition.key] = next;
|
|
201
|
+
index += 1;
|
|
202
|
+
} else {
|
|
203
|
+
options[definition.key] = value;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
continue;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
if (value === undefined) {
|
|
210
|
+
options[definition.key] = true;
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if (value === 'true' || value === 'false') {
|
|
215
|
+
options[definition.key] = value === 'true';
|
|
216
|
+
continue;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
if (value !== undefined) {
|
|
220
|
+
errors.push(`Option --${name} expects a boolean value.`);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
return {command, commandArgs, options, errors};
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function normalizeOptions(parsed) {
|
|
228
|
+
const errors = [];
|
|
229
|
+
const result = {
|
|
230
|
+
...parsed.options,
|
|
231
|
+
cacheDir: parsed.options.cacheDir
|
|
232
|
+
? path.resolve(process.cwd(), parsed.options.cacheDir)
|
|
233
|
+
: DEFAULT_CACHE_DIR,
|
|
234
|
+
refresh: Boolean(parsed.options.refresh),
|
|
235
|
+
noCache: Boolean(parsed.options.noCache),
|
|
236
|
+
force: Boolean(parsed.options.force),
|
|
237
|
+
pretty: Boolean(parsed.options.pretty),
|
|
238
|
+
help: Boolean(parsed.options.help),
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
const ttl = parseInteger(parsed.options.ttlMs, 'ttl-ms', 0);
|
|
242
|
+
if (ttl.error) {
|
|
243
|
+
errors.push(ttl.error);
|
|
244
|
+
} else if (ttl.value !== undefined) {
|
|
245
|
+
result.ttlMs = ttl.value;
|
|
246
|
+
} else {
|
|
247
|
+
result.ttlMs = DEFAULT_TTL_MS;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
const limit = parseInteger(parsed.options.limit, 'limit', 0, true);
|
|
251
|
+
if (limit.error) {
|
|
252
|
+
errors.push(limit.error);
|
|
253
|
+
} else if (limit.value !== undefined) {
|
|
254
|
+
result.limit = limit.value;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
const offset = parseInteger(parsed.options.offset, 'offset', 0, true);
|
|
258
|
+
if (offset.error) {
|
|
259
|
+
errors.push(offset.error);
|
|
260
|
+
} else if (offset.value !== undefined) {
|
|
261
|
+
result.offset = offset.value;
|
|
262
|
+
} else {
|
|
263
|
+
result.offset = 0;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
const maxChars = parseInteger(parsed.options.maxChars, 'max-chars', 0, true);
|
|
267
|
+
if (maxChars.error) {
|
|
268
|
+
errors.push(maxChars.error);
|
|
269
|
+
} else {
|
|
270
|
+
result.maxChars = maxChars.value;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
return {
|
|
274
|
+
command: parsed.command,
|
|
275
|
+
commandArgs: parsed.commandArgs,
|
|
276
|
+
options: result,
|
|
277
|
+
errors,
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
function parseInteger(value, name, min, allowEmpty = false) {
|
|
282
|
+
if (value === undefined) {
|
|
283
|
+
if (allowEmpty) {
|
|
284
|
+
return {value: undefined};
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
return {value: undefined};
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
const raw = String(value).trim();
|
|
291
|
+
if (!raw) {
|
|
292
|
+
return {error: `Option ${name} requires a numeric value.`};
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
const parsed = Number.parseInt(raw, 10);
|
|
296
|
+
|
|
297
|
+
if (!Number.isFinite(parsed) || `${parsed}` !== raw || parsed < min) {
|
|
298
|
+
return {error: `Option ${name} must be an integer >= ${min}.`};
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
return {value: parsed};
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
function resolveSourceConfig(options) {
|
|
305
|
+
const resolvedSourceFile = options.sourceFile ?? process.env.TAIGA_UI_DOCS_SOURCE_FILE;
|
|
306
|
+
const resolvedSourceUrl =
|
|
307
|
+
options.sourceUrl ??
|
|
308
|
+
process.env.TAIGA_UI_DOCS_SOURCE_URL ??
|
|
309
|
+
process.env.SOURCE_URL ??
|
|
310
|
+
DEFAULT_SOURCE_URL;
|
|
311
|
+
|
|
312
|
+
return {
|
|
313
|
+
sourceFile: resolvedSourceFile,
|
|
314
|
+
sourceUrl: resolvedSourceUrl,
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
async function loadSource(config, runtimeOptions = {}) {
|
|
319
|
+
const cacheEnabled = !runtimeOptions.noCache;
|
|
320
|
+
const cacheDir = runtimeOptions.cacheDir || DEFAULT_CACHE_DIR;
|
|
321
|
+
|
|
322
|
+
if (config.sourceFile) {
|
|
323
|
+
const sourcePath = path.resolve(process.cwd(), config.sourceFile);
|
|
324
|
+
let content = '';
|
|
325
|
+
|
|
326
|
+
try {
|
|
327
|
+
content = await fs.readFile(sourcePath, 'utf8');
|
|
328
|
+
} catch (error) {
|
|
329
|
+
throw new Error(
|
|
330
|
+
`Failed to read source file ${sourcePath}: ${error instanceof Error ? error.message : String(error)}`,
|
|
331
|
+
);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
if (!content.trim()) {
|
|
335
|
+
throw new Error(`Source file ${sourcePath} is empty.`);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
return {
|
|
339
|
+
source: {
|
|
340
|
+
type: 'file',
|
|
341
|
+
value: sourcePath,
|
|
342
|
+
sourceUrl: sourcePath,
|
|
343
|
+
cached: false,
|
|
344
|
+
loadedAt: Date.now(),
|
|
345
|
+
},
|
|
346
|
+
content,
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
const sourceUrl = config.sourceUrl;
|
|
351
|
+
|
|
352
|
+
if (!sourceUrl) {
|
|
353
|
+
throw new Error('No source URL configured for remote load.');
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
if (!runtimeOptions.refresh && cacheEnabled) {
|
|
357
|
+
const cached = await loadCachedSource(sourceUrl, cacheDir, runtimeOptions.ttlMs);
|
|
358
|
+
|
|
359
|
+
if (cached) {
|
|
360
|
+
return {
|
|
361
|
+
source: {
|
|
362
|
+
type: 'remote',
|
|
363
|
+
value: sourceUrl,
|
|
364
|
+
sourceUrl,
|
|
365
|
+
cached: true,
|
|
366
|
+
loadedAt: cached.loadedAt,
|
|
367
|
+
},
|
|
368
|
+
content: cached.content,
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
let response;
|
|
374
|
+
|
|
375
|
+
try {
|
|
376
|
+
response = await fetch(sourceUrl);
|
|
377
|
+
} catch (error) {
|
|
378
|
+
throw new Error(
|
|
379
|
+
`Network error fetching documentation source: ${error instanceof Error ? error.message : String(error)}`,
|
|
380
|
+
);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
if (!response.ok) {
|
|
384
|
+
throw new Error(
|
|
385
|
+
`Failed to fetch documentation (HTTP ${response.status} ${response.statusText}) from ${sourceUrl}`,
|
|
386
|
+
);
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
const content = await response.text();
|
|
390
|
+
|
|
391
|
+
if (!content.trim()) {
|
|
392
|
+
throw new Error(`Fetched documentation from ${sourceUrl} is empty.`);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
const loadedAt = Date.now();
|
|
396
|
+
|
|
397
|
+
if (cacheEnabled) {
|
|
398
|
+
await saveCachedSource(sourceUrl, content, cacheDir, loadedAt);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
return {
|
|
402
|
+
source: {
|
|
403
|
+
type: 'remote',
|
|
404
|
+
value: sourceUrl,
|
|
405
|
+
sourceUrl,
|
|
406
|
+
cached: false,
|
|
407
|
+
loadedAt,
|
|
408
|
+
},
|
|
409
|
+
content,
|
|
410
|
+
};
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
async function loadCachedSource(sourceUrl, cacheDir, ttlMs) {
|
|
414
|
+
try {
|
|
415
|
+
const [metaText, sourceText] = await Promise.all([
|
|
416
|
+
fs.readFile(path.join(cacheDir, CACHE_META_FILE), 'utf8'),
|
|
417
|
+
fs.readFile(path.join(cacheDir, CACHE_SOURCE_FILE), 'utf8'),
|
|
418
|
+
]);
|
|
419
|
+
|
|
420
|
+
const meta = JSON.parse(metaText);
|
|
421
|
+
|
|
422
|
+
if (meta.sourceUrl !== sourceUrl) {
|
|
423
|
+
return null;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
const loadedAt = Number(meta.loadedAt);
|
|
427
|
+
|
|
428
|
+
if (!Number.isFinite(loadedAt)) {
|
|
429
|
+
return null;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
if (Date.now() - loadedAt > ttlMs) {
|
|
433
|
+
return null;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
if (!sourceText.trim()) {
|
|
437
|
+
return null;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
return {content: sourceText, loadedAt};
|
|
441
|
+
} catch {
|
|
442
|
+
return null;
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
async function saveCachedSource(sourceUrl, content, cacheDir, loadedAt) {
|
|
447
|
+
try {
|
|
448
|
+
await fs.mkdir(cacheDir, {recursive: true});
|
|
449
|
+
await Promise.all([
|
|
450
|
+
fs.writeFile(
|
|
451
|
+
path.join(cacheDir, CACHE_SOURCE_FILE),
|
|
452
|
+
content,
|
|
453
|
+
'utf8',
|
|
454
|
+
),
|
|
455
|
+
fs.writeFile(
|
|
456
|
+
path.join(cacheDir, CACHE_META_FILE),
|
|
457
|
+
JSON.stringify({sourceUrl, loadedAt}),
|
|
458
|
+
'utf8',
|
|
459
|
+
),
|
|
460
|
+
]);
|
|
461
|
+
} catch {
|
|
462
|
+
// Cache errors are non-fatal; command can still work.
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
function parseContent(rawContent, source) {
|
|
467
|
+
if (!rawContent.trim()) {
|
|
468
|
+
throw new Error('parseContent: rawContent is empty');
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
const headerContent = extractHeaderContent(rawContent);
|
|
472
|
+
const migrationGuideContent = extractMigrationGuideContent(rawContent);
|
|
473
|
+
const lines = rawContent.split(/\r?\n/);
|
|
474
|
+
const componentsStartLine = findComponentsSectionStart(rawContent);
|
|
475
|
+
const headerIndices = [];
|
|
476
|
+
|
|
477
|
+
for (let lineIndex = componentsStartLine; lineIndex < lines.length; lineIndex++) {
|
|
478
|
+
const line = lines[lineIndex];
|
|
479
|
+
|
|
480
|
+
if (!line) {
|
|
481
|
+
continue;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
const headerMatch = /^#\s+(\S.*)$/.exec(line);
|
|
485
|
+
|
|
486
|
+
if (headerMatch?.[1]) {
|
|
487
|
+
headerIndices.push({
|
|
488
|
+
line: lineIndex,
|
|
489
|
+
title: headerMatch[1].trim(),
|
|
490
|
+
});
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
const sections = headerIndices.map((header, index) => {
|
|
495
|
+
const start = header.line;
|
|
496
|
+
const end = headerIndices[index + 1]?.line ?? lines.length;
|
|
497
|
+
const sectionContent = lines.slice(start, end).join('\n');
|
|
498
|
+
const metadata = extractSectionMeta(sectionContent);
|
|
499
|
+
|
|
500
|
+
return {
|
|
501
|
+
id: header.title,
|
|
502
|
+
title: header.title,
|
|
503
|
+
content: sectionContent,
|
|
504
|
+
package: metadata.package,
|
|
505
|
+
kind: metadata.kind,
|
|
506
|
+
};
|
|
507
|
+
});
|
|
508
|
+
|
|
509
|
+
return {
|
|
510
|
+
source,
|
|
511
|
+
overview: headerContent,
|
|
512
|
+
migrationGuide: migrationGuideContent,
|
|
513
|
+
sections,
|
|
514
|
+
};
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
function extractSectionMeta(text) {
|
|
518
|
+
let packageValue;
|
|
519
|
+
let kind;
|
|
520
|
+
|
|
521
|
+
const packageMatch = /\*\*Package\*\*:\s*`([^`]+)`/i.exec(text);
|
|
522
|
+
if (packageMatch?.[1]) {
|
|
523
|
+
packageValue = packageMatch[1];
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
const typeMatch = /\*\*Type\*\*:\s*([^\n]+)/i.exec(text);
|
|
527
|
+
if (typeMatch?.[1]) {
|
|
528
|
+
kind = typeMatch[1].trim();
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
return {package: packageValue, kind};
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
function extractHeaderContent(rawContent) {
|
|
535
|
+
const lines = rawContent.split(/\r?\n/);
|
|
536
|
+
const headerLines = [];
|
|
537
|
+
|
|
538
|
+
for (const line of lines) {
|
|
539
|
+
if (/^#\s+components\//.test(line)) {
|
|
540
|
+
break;
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
headerLines.push(line);
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
return headerLines.join('\n').trim();
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
function findComponentsSectionStart(rawContent) {
|
|
550
|
+
const lines = rawContent.split(/\r?\n/);
|
|
551
|
+
|
|
552
|
+
for (let i = 0; i < lines.length; i++) {
|
|
553
|
+
const line = lines[i];
|
|
554
|
+
|
|
555
|
+
if (line && /^#\s+components\//.test(line)) {
|
|
556
|
+
return i;
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
return lines.length;
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
function parseHeaderSections(headerContent) {
|
|
564
|
+
const lines = headerContent.split(/\r?\n/);
|
|
565
|
+
const sections = [];
|
|
566
|
+
const title = 'Taiga UI - Complete Documentation';
|
|
567
|
+
|
|
568
|
+
const sectionIndices = [];
|
|
569
|
+
|
|
570
|
+
for (let i = 0; i < lines.length; i++) {
|
|
571
|
+
const line = lines[i];
|
|
572
|
+
|
|
573
|
+
if (!line) {
|
|
574
|
+
continue;
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
const h1Match = /^#\s+([^#]\S.*)$/.exec(line);
|
|
578
|
+
|
|
579
|
+
if (h1Match?.[1]) {
|
|
580
|
+
sectionIndices.push({
|
|
581
|
+
line: i,
|
|
582
|
+
title: h1Match[1].trim(),
|
|
583
|
+
});
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
for (let i = 0; i < sectionIndices.length; i++) {
|
|
588
|
+
const currentSection = sectionIndices[i];
|
|
589
|
+
|
|
590
|
+
if (!currentSection) {
|
|
591
|
+
continue;
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
const start = currentSection.line;
|
|
595
|
+
const end = sectionIndices[i + 1]?.line ?? lines.length;
|
|
596
|
+
const sectionContent = lines.slice(start, end).join('\n');
|
|
597
|
+
const parsedSection = parseHeaderSection(sectionContent);
|
|
598
|
+
|
|
599
|
+
sections.push(parsedSection);
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
const compactedSections = sections.map((section) => {
|
|
603
|
+
const hasMarkdownSubsections = section.subsections.some((subsection) =>
|
|
604
|
+
subsection.title.endsWith('.md'),
|
|
605
|
+
);
|
|
606
|
+
|
|
607
|
+
return hasMarkdownSubsections
|
|
608
|
+
? {
|
|
609
|
+
...section,
|
|
610
|
+
subsections: section.subsections.map((subsection) => {
|
|
611
|
+
const redundantSingleCodeSection =
|
|
612
|
+
subsection.content.length === 0 &&
|
|
613
|
+
!subsection.items?.length &&
|
|
614
|
+
subsection.sections?.length === 1 &&
|
|
615
|
+
subsection.sections[0]?.section === subsection.title &&
|
|
616
|
+
Boolean(subsection.sections[0].code);
|
|
617
|
+
|
|
618
|
+
return redundantSingleCodeSection
|
|
619
|
+
? {
|
|
620
|
+
title: subsection.title,
|
|
621
|
+
content: [subsection.sections?.[0]?.code ?? ''],
|
|
622
|
+
}
|
|
623
|
+
: subsection;
|
|
624
|
+
}),
|
|
625
|
+
}
|
|
626
|
+
: section;
|
|
627
|
+
});
|
|
628
|
+
|
|
629
|
+
return {
|
|
630
|
+
title,
|
|
631
|
+
sections: compactedSections,
|
|
632
|
+
};
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
function extractCodeBlocks(content) {
|
|
636
|
+
const codeBlocks = [];
|
|
637
|
+
const lines = content.split('\n');
|
|
638
|
+
let inCodeBlock = false;
|
|
639
|
+
let currentLang = '';
|
|
640
|
+
let currentCode = [];
|
|
641
|
+
|
|
642
|
+
for (const line of lines) {
|
|
643
|
+
if (line.startsWith('```')) {
|
|
644
|
+
if (inCodeBlock) {
|
|
645
|
+
codeBlocks.push({
|
|
646
|
+
language: currentLang,
|
|
647
|
+
code: currentCode.join('\n').trim(),
|
|
648
|
+
});
|
|
649
|
+
currentCode = [];
|
|
650
|
+
inCodeBlock = false;
|
|
651
|
+
} else {
|
|
652
|
+
currentLang = line.slice(3).trim() || 'plaintext';
|
|
653
|
+
inCodeBlock = true;
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
continue;
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
if (inCodeBlock) {
|
|
660
|
+
currentCode.push(line);
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
return codeBlocks;
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
function extractPlainContent(content) {
|
|
668
|
+
const lines = content.split('\n');
|
|
669
|
+
const plainLines = [];
|
|
670
|
+
let inCodeBlock = false;
|
|
671
|
+
|
|
672
|
+
for (const line of lines) {
|
|
673
|
+
if (line.startsWith('```')) {
|
|
674
|
+
inCodeBlock = !inCodeBlock;
|
|
675
|
+
continue;
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
if (inCodeBlock) {
|
|
679
|
+
continue;
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
if (!line.trim()) {
|
|
683
|
+
continue;
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
if (/^#{1,6}\s+/.test(line)) {
|
|
687
|
+
continue;
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
if (/^-{3,}\s*$/.test(line)) {
|
|
691
|
+
continue;
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
if (line.includes('**Critical**:') || line.includes('**Auto-generated**:')) {
|
|
695
|
+
continue;
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
let cleaned = line.replace(/^>\s*/, '').trim();
|
|
699
|
+
cleaned = cleaned.replaceAll(/\*\*([^*]+)\*\*/g, '$1');
|
|
700
|
+
cleaned = cleaned.replaceAll(/\*([^*]+)\*/g, '$1');
|
|
701
|
+
cleaned = cleaned.replaceAll(/`([^`]+)`/g, '$1');
|
|
702
|
+
|
|
703
|
+
if (cleaned) {
|
|
704
|
+
plainLines.push(cleaned);
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
return plainLines;
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
function parseHeaderSection(content) {
|
|
712
|
+
const lines = content.split('\n');
|
|
713
|
+
const subsections = [];
|
|
714
|
+
const description = [];
|
|
715
|
+
const criticalNotices = [];
|
|
716
|
+
let title = '';
|
|
717
|
+
|
|
718
|
+
const headings = [];
|
|
719
|
+
|
|
720
|
+
for (let i = 0; i < lines.length; i++) {
|
|
721
|
+
const line = lines[i];
|
|
722
|
+
|
|
723
|
+
if (!line) {
|
|
724
|
+
continue;
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
const h1Match = /^#\s+([^#]\S.*)$/.exec(line);
|
|
728
|
+
|
|
729
|
+
if (h1Match?.[1] && !title) {
|
|
730
|
+
title = h1Match[1].trim();
|
|
731
|
+
headings.push({line: i, level: 1, title: h1Match[1].trim()});
|
|
732
|
+
continue;
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
const hMatch = /^(#{2,6})\s+(\S.*)$/.exec(line);
|
|
736
|
+
|
|
737
|
+
if (hMatch?.[1] && hMatch[2]) {
|
|
738
|
+
headings.push({
|
|
739
|
+
line: i,
|
|
740
|
+
level: hMatch[1].length,
|
|
741
|
+
title: hMatch[2].trim(),
|
|
742
|
+
});
|
|
743
|
+
|
|
744
|
+
continue;
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
const boldMatch = /^\*\*(.+):\*\*$/.exec(line);
|
|
748
|
+
|
|
749
|
+
if (boldMatch?.[1]) {
|
|
750
|
+
headings.push({
|
|
751
|
+
line: i,
|
|
752
|
+
level: 2,
|
|
753
|
+
title: `${boldMatch[1].trim()}:`,
|
|
754
|
+
});
|
|
755
|
+
|
|
756
|
+
continue;
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
if (line.startsWith('> **Critical**:')) {
|
|
760
|
+
const notice = line.replace(/^>\s*\*\*Critical\*\*:\s*/, '').trim();
|
|
761
|
+
|
|
762
|
+
if (notice) {
|
|
763
|
+
criticalNotices.push(notice);
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
if (headings.length > 0 && headings[0]) {
|
|
769
|
+
const firstSubsectionLine = headings.find((h) => h.level > 1)?.line;
|
|
770
|
+
const descEnd = firstSubsectionLine ?? lines.length;
|
|
771
|
+
const descContent = lines.slice(headings[0].line + 1, descEnd).join('\n');
|
|
772
|
+
|
|
773
|
+
description.push(...extractPlainContent(descContent));
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
let currentSubsection = null;
|
|
777
|
+
let currentItem = null;
|
|
778
|
+
let currentGroup = null;
|
|
779
|
+
|
|
780
|
+
const pushCurrentItem = () => {
|
|
781
|
+
if (!currentItem || !currentSubsection) {
|
|
782
|
+
return;
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
if (
|
|
786
|
+
currentItem.content.length > 0 ||
|
|
787
|
+
currentItem.code ||
|
|
788
|
+
(currentItem.sections && currentItem.sections.length > 0)
|
|
789
|
+
) {
|
|
790
|
+
currentSubsection.items = currentSubsection.items || [];
|
|
791
|
+
currentSubsection.items.push(currentItem);
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
currentItem = null;
|
|
795
|
+
};
|
|
796
|
+
|
|
797
|
+
const pushCurrentGroup = () => {
|
|
798
|
+
if (!currentGroup || !currentSubsection) {
|
|
799
|
+
return;
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
if (
|
|
803
|
+
currentGroup.content.length > 0 ||
|
|
804
|
+
(currentGroup.sections && currentGroup.sections.length > 0)
|
|
805
|
+
) {
|
|
806
|
+
currentSubsection.items = currentSubsection.items || [];
|
|
807
|
+
currentSubsection.items.push(currentGroup);
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
currentGroup = null;
|
|
811
|
+
};
|
|
812
|
+
|
|
813
|
+
const addSectionToItem = (item, sectionItem, plainContent) => {
|
|
814
|
+
item.sections = item.sections || [];
|
|
815
|
+
item.sections.push(sectionItem);
|
|
816
|
+
|
|
817
|
+
if (plainContent.length > 0) {
|
|
818
|
+
item.content.push(...plainContent);
|
|
819
|
+
}
|
|
820
|
+
};
|
|
821
|
+
|
|
822
|
+
for (let i = 1; i < headings.length; i++) {
|
|
823
|
+
const current = headings[i];
|
|
824
|
+
|
|
825
|
+
if (!current) {
|
|
826
|
+
continue;
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
const start = current.line;
|
|
830
|
+
const end = headings[i + 1]?.line ?? lines.length;
|
|
831
|
+
const subsectionContent = lines.slice(start + 1, end).join('\n');
|
|
832
|
+
|
|
833
|
+
if (current.level === 2 && !current.title.endsWith(':')) {
|
|
834
|
+
pushCurrentItem();
|
|
835
|
+
pushCurrentGroup();
|
|
836
|
+
|
|
837
|
+
if (currentSubsection) {
|
|
838
|
+
if (
|
|
839
|
+
currentSubsection.content.length > 0 ||
|
|
840
|
+
(currentSubsection.sections && currentSubsection.sections.length > 0) ||
|
|
841
|
+
(currentSubsection.items && currentSubsection.items.length > 0)
|
|
842
|
+
) {
|
|
843
|
+
subsections.push(currentSubsection);
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
const [block] = extractCodeBlocks(subsectionContent);
|
|
848
|
+
const subsectionSections = block
|
|
849
|
+
? [
|
|
850
|
+
{
|
|
851
|
+
section: current.title,
|
|
852
|
+
code: block.code,
|
|
853
|
+
},
|
|
854
|
+
]
|
|
855
|
+
: [];
|
|
856
|
+
|
|
857
|
+
currentSubsection = {
|
|
858
|
+
title: current.title,
|
|
859
|
+
content: extractPlainContent(subsectionContent),
|
|
860
|
+
sections: subsectionSections,
|
|
861
|
+
items: [],
|
|
862
|
+
};
|
|
863
|
+
|
|
864
|
+
continue;
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
if (current.level === 3 && !current.title.endsWith(':')) {
|
|
868
|
+
const plainContent = extractPlainContent(subsectionContent);
|
|
869
|
+
const codeBlocks = extractCodeBlocks(subsectionContent);
|
|
870
|
+
const code = codeBlocks.length > 0 ? codeBlocks[0]?.code : undefined;
|
|
871
|
+
|
|
872
|
+
if (currentGroup && currentSubsection) {
|
|
873
|
+
const sectionItem = {
|
|
874
|
+
section: current.title,
|
|
875
|
+
code,
|
|
876
|
+
};
|
|
877
|
+
|
|
878
|
+
currentGroup.sections = currentGroup.sections || [];
|
|
879
|
+
currentGroup.sections.push(sectionItem);
|
|
880
|
+
|
|
881
|
+
if (plainContent.length > 0) {
|
|
882
|
+
currentGroup.content.push(...plainContent);
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
continue;
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
pushCurrentItem();
|
|
889
|
+
|
|
890
|
+
currentItem = {
|
|
891
|
+
title: current.title,
|
|
892
|
+
content: plainContent,
|
|
893
|
+
...(code ? {code} : {}),
|
|
894
|
+
sections: [],
|
|
895
|
+
};
|
|
896
|
+
|
|
897
|
+
continue;
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
if (current.title.endsWith(':')) {
|
|
901
|
+
const codeBlocks = extractCodeBlocks(subsectionContent);
|
|
902
|
+
const code = codeBlocks.length > 0 ? codeBlocks[0]?.code : undefined;
|
|
903
|
+
const plainContent = extractPlainContent(subsectionContent);
|
|
904
|
+
const nextHeading = headings[i + 1];
|
|
905
|
+
|
|
906
|
+
if (currentSubsection && !currentItem && nextHeading?.level === 3) {
|
|
907
|
+
pushCurrentGroup();
|
|
908
|
+
|
|
909
|
+
currentGroup = {
|
|
910
|
+
title: current.title.replace(/:\s*$/, ''),
|
|
911
|
+
content: plainContent,
|
|
912
|
+
...(code ? {code} : {}),
|
|
913
|
+
sections: [],
|
|
914
|
+
};
|
|
915
|
+
currentItem = null;
|
|
916
|
+
|
|
917
|
+
continue;
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
const sectionItem = {
|
|
921
|
+
section: current.title,
|
|
922
|
+
code,
|
|
923
|
+
};
|
|
924
|
+
|
|
925
|
+
if (currentItem) {
|
|
926
|
+
addSectionToItem(currentItem, sectionItem, plainContent);
|
|
927
|
+
continue;
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
if (currentSubsection) {
|
|
931
|
+
currentSubsection.sections = currentSubsection.sections || [];
|
|
932
|
+
currentSubsection.sections.push(sectionItem);
|
|
933
|
+
continue;
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
subsections.push({
|
|
937
|
+
title: current.title,
|
|
938
|
+
content: plainContent,
|
|
939
|
+
sections: code ? [sectionItem] : undefined,
|
|
940
|
+
});
|
|
941
|
+
|
|
942
|
+
continue;
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
pushCurrentItem();
|
|
946
|
+
pushCurrentGroup();
|
|
947
|
+
|
|
948
|
+
if (currentSubsection) {
|
|
949
|
+
subsections.push(currentSubsection);
|
|
950
|
+
currentSubsection = null;
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
const plainContent = extractPlainContent(subsectionContent);
|
|
954
|
+
const codeBlocks = extractCodeBlocks(subsectionContent);
|
|
955
|
+
|
|
956
|
+
subsections.push({
|
|
957
|
+
title: current.title,
|
|
958
|
+
content: plainContent,
|
|
959
|
+
sections:
|
|
960
|
+
codeBlocks.length > 0
|
|
961
|
+
? [{section: current.title, code: codeBlocks[0]?.code}]
|
|
962
|
+
: undefined,
|
|
963
|
+
});
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
pushCurrentItem();
|
|
967
|
+
pushCurrentGroup();
|
|
968
|
+
|
|
969
|
+
if (currentSubsection) {
|
|
970
|
+
if (
|
|
971
|
+
currentSubsection.content.length > 0 ||
|
|
972
|
+
(currentSubsection.sections && currentSubsection.sections.length > 0) ||
|
|
973
|
+
(currentSubsection.items && currentSubsection.items.length > 0)
|
|
974
|
+
) {
|
|
975
|
+
subsections.push(currentSubsection);
|
|
976
|
+
}
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
if (subsections.length === 0 && headings.length === 1 && headings[0]) {
|
|
980
|
+
const contentAfterTitle = lines.slice(headings[0].line + 1).join('\n');
|
|
981
|
+
const codeBlocks = extractCodeBlocks(contentAfterTitle);
|
|
982
|
+
|
|
983
|
+
if (codeBlocks.length > 0 && codeBlocks[0]) {
|
|
984
|
+
subsections.push({
|
|
985
|
+
title: '',
|
|
986
|
+
content: [codeBlocks[0].code],
|
|
987
|
+
});
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
const descriptionText = description.join('\n');
|
|
992
|
+
|
|
993
|
+
return {
|
|
994
|
+
title,
|
|
995
|
+
description: descriptionText || '',
|
|
996
|
+
criticalNotices,
|
|
997
|
+
subsections,
|
|
998
|
+
};
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
function extractMigrationGuideContent(rawContent) {
|
|
1002
|
+
if (!rawContent.trim()) {
|
|
1003
|
+
return undefined;
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
const lines = rawContent.split(/\r?\n/);
|
|
1007
|
+
let startIndex = -1;
|
|
1008
|
+
let endIndex = lines.length;
|
|
1009
|
+
const migrationGuideRegex = /^#\s+Migration\s+Guide/i;
|
|
1010
|
+
const componentsRegex = /^#\s+components\//;
|
|
1011
|
+
|
|
1012
|
+
for (let i = 0; i < lines.length; i++) {
|
|
1013
|
+
const line = lines[i];
|
|
1014
|
+
|
|
1015
|
+
if (!line) {
|
|
1016
|
+
continue;
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
if (line === '---' && migrationGuideRegex.exec(lines[i + 1] ?? '')) {
|
|
1020
|
+
startIndex = i;
|
|
1021
|
+
continue;
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
if (startIndex !== -1 && componentsRegex.exec(line)) {
|
|
1025
|
+
endIndex = i;
|
|
1026
|
+
break;
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
return startIndex === -1 ? undefined : lines.slice(startIndex, endIndex).join('\n').trim();
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
function parseMigrationGuide(migrationContent) {
|
|
1034
|
+
const lines = migrationContent.split(/\r?\n/);
|
|
1035
|
+
let title = 'Migration Guide';
|
|
1036
|
+
const introduction = [];
|
|
1037
|
+
const sections = [];
|
|
1038
|
+
let currentSection = null;
|
|
1039
|
+
let currentSubsection = null;
|
|
1040
|
+
let inCodeBlock = false;
|
|
1041
|
+
let inHtmlComment = false;
|
|
1042
|
+
let currentCode = [];
|
|
1043
|
+
|
|
1044
|
+
const appendCode = (code) => {
|
|
1045
|
+
if (currentSubsection) {
|
|
1046
|
+
currentSubsection.codeBlocks = currentSubsection.codeBlocks || [];
|
|
1047
|
+
currentSubsection.codeBlocks.push(code);
|
|
1048
|
+
|
|
1049
|
+
return;
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
if (currentSection) {
|
|
1053
|
+
currentSection.codeBlocks = currentSection.codeBlocks || [];
|
|
1054
|
+
currentSection.codeBlocks.push(code);
|
|
1055
|
+
}
|
|
1056
|
+
};
|
|
1057
|
+
|
|
1058
|
+
const pushCurrentSubsection = () => {
|
|
1059
|
+
if (!currentSubsection || !currentSection) {
|
|
1060
|
+
return;
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1063
|
+
if (
|
|
1064
|
+
currentSubsection.content.length > 0 ||
|
|
1065
|
+
(currentSubsection.codeBlocks && currentSubsection.codeBlocks.length > 0)
|
|
1066
|
+
) {
|
|
1067
|
+
currentSection.subsections = currentSection.subsections || [];
|
|
1068
|
+
currentSection.subsections.push(currentSubsection);
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1071
|
+
currentSubsection = null;
|
|
1072
|
+
};
|
|
1073
|
+
|
|
1074
|
+
const pushCurrentSection = () => {
|
|
1075
|
+
if (!currentSection) {
|
|
1076
|
+
return;
|
|
1077
|
+
}
|
|
1078
|
+
|
|
1079
|
+
pushCurrentSubsection();
|
|
1080
|
+
|
|
1081
|
+
if (
|
|
1082
|
+
currentSection.content.length > 0 ||
|
|
1083
|
+
(currentSection.codeBlocks && currentSection.codeBlocks.length > 0) ||
|
|
1084
|
+
(currentSection.subsections && currentSection.subsections.length > 0)
|
|
1085
|
+
) {
|
|
1086
|
+
sections.push(currentSection);
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1089
|
+
currentSection = null;
|
|
1090
|
+
};
|
|
1091
|
+
|
|
1092
|
+
for (const line of lines) {
|
|
1093
|
+
if (line.startsWith('```')) {
|
|
1094
|
+
if (inCodeBlock) {
|
|
1095
|
+
inCodeBlock = false;
|
|
1096
|
+
|
|
1097
|
+
const code = currentCode.join('\n');
|
|
1098
|
+
|
|
1099
|
+
if (code || currentCode.length > 0) {
|
|
1100
|
+
appendCode(code);
|
|
1101
|
+
}
|
|
1102
|
+
|
|
1103
|
+
currentCode = [];
|
|
1104
|
+
} else {
|
|
1105
|
+
inCodeBlock = true;
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1108
|
+
continue;
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1111
|
+
if (inCodeBlock) {
|
|
1112
|
+
currentCode.push(line);
|
|
1113
|
+
continue;
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
if (inHtmlComment) {
|
|
1117
|
+
if (line.includes('-->')) {
|
|
1118
|
+
inHtmlComment = false;
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
continue;
|
|
1122
|
+
}
|
|
1123
|
+
|
|
1124
|
+
if (line.startsWith('<!--')) {
|
|
1125
|
+
inHtmlComment = !line.includes('-->');
|
|
1126
|
+
continue;
|
|
1127
|
+
}
|
|
1128
|
+
|
|
1129
|
+
if (!line || line === '---') {
|
|
1130
|
+
continue;
|
|
1131
|
+
}
|
|
1132
|
+
|
|
1133
|
+
const h1Match = /^# ([^#\n]+)$/.exec(line);
|
|
1134
|
+
if (h1Match?.[1]) {
|
|
1135
|
+
pushCurrentSection();
|
|
1136
|
+
title = h1Match[1].trim();
|
|
1137
|
+
continue;
|
|
1138
|
+
}
|
|
1139
|
+
|
|
1140
|
+
const h2Match = /^## ([^#\n]+)$/.exec(line);
|
|
1141
|
+
if (h2Match?.[1]) {
|
|
1142
|
+
pushCurrentSection();
|
|
1143
|
+
|
|
1144
|
+
currentSection = {
|
|
1145
|
+
title: h2Match[1].trim(),
|
|
1146
|
+
content: [],
|
|
1147
|
+
};
|
|
1148
|
+
|
|
1149
|
+
continue;
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1152
|
+
const h3Match = /^### ([^#\n]+)$/.exec(line);
|
|
1153
|
+
if (h3Match?.[1]) {
|
|
1154
|
+
pushCurrentSubsection();
|
|
1155
|
+
|
|
1156
|
+
currentSubsection = {
|
|
1157
|
+
title: h3Match[1].trim(),
|
|
1158
|
+
content: [],
|
|
1159
|
+
};
|
|
1160
|
+
|
|
1161
|
+
continue;
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1164
|
+
if (line.startsWith('>')) {
|
|
1165
|
+
const cleaned = line.replace(/^>\s*/, '').trim();
|
|
1166
|
+
|
|
1167
|
+
if (!cleaned) {
|
|
1168
|
+
continue;
|
|
1169
|
+
}
|
|
1170
|
+
|
|
1171
|
+
if (currentSubsection) {
|
|
1172
|
+
currentSubsection.content.push(cleaned);
|
|
1173
|
+
} else if (currentSection) {
|
|
1174
|
+
currentSection.content.push(cleaned);
|
|
1175
|
+
} else {
|
|
1176
|
+
introduction.push(cleaned);
|
|
1177
|
+
}
|
|
1178
|
+
|
|
1179
|
+
continue;
|
|
1180
|
+
}
|
|
1181
|
+
|
|
1182
|
+
if (line.startsWith('- ')) {
|
|
1183
|
+
const cleaned = line.slice(2).trim();
|
|
1184
|
+
|
|
1185
|
+
if (!cleaned) {
|
|
1186
|
+
continue;
|
|
1187
|
+
}
|
|
1188
|
+
|
|
1189
|
+
const item = `- ${cleaned}`;
|
|
1190
|
+
|
|
1191
|
+
if (currentSubsection) {
|
|
1192
|
+
currentSubsection.content.push(item);
|
|
1193
|
+
} else if (currentSection) {
|
|
1194
|
+
currentSection.content.push(item);
|
|
1195
|
+
} else {
|
|
1196
|
+
introduction.push(item);
|
|
1197
|
+
}
|
|
1198
|
+
|
|
1199
|
+
continue;
|
|
1200
|
+
}
|
|
1201
|
+
|
|
1202
|
+
if (line.startsWith('---')) {
|
|
1203
|
+
continue;
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1206
|
+
const cleaned = line.trim();
|
|
1207
|
+
|
|
1208
|
+
if (cleaned) {
|
|
1209
|
+
if (currentSubsection) {
|
|
1210
|
+
currentSubsection.content.push(cleaned);
|
|
1211
|
+
} else if (currentSection) {
|
|
1212
|
+
currentSection.content.push(cleaned);
|
|
1213
|
+
} else {
|
|
1214
|
+
introduction.push(cleaned);
|
|
1215
|
+
}
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1218
|
+
|
|
1219
|
+
if (inCodeBlock && currentCode.length > 0) {
|
|
1220
|
+
appendCode(currentCode.join('\n'));
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1223
|
+
pushCurrentSubsection();
|
|
1224
|
+
pushCurrentSection();
|
|
1225
|
+
|
|
1226
|
+
return {
|
|
1227
|
+
title,
|
|
1228
|
+
introduction,
|
|
1229
|
+
sections,
|
|
1230
|
+
};
|
|
1231
|
+
}
|
|
1232
|
+
|
|
1233
|
+
function constructComponentsList(sections, query = '') {
|
|
1234
|
+
const normalizedQuery = query.toLowerCase().replace(/^tui/, '');
|
|
1235
|
+
|
|
1236
|
+
return sections
|
|
1237
|
+
.filter((section) =>
|
|
1238
|
+
!normalizedQuery || section.id.toLowerCase().includes(normalizedQuery),
|
|
1239
|
+
)
|
|
1240
|
+
.map((section) => {
|
|
1241
|
+
const idParts = section.id.split('/');
|
|
1242
|
+
const name = idParts[idParts.length - 1] ?? section.id;
|
|
1243
|
+
const category = idParts[0] ?? '';
|
|
1244
|
+
|
|
1245
|
+
return {
|
|
1246
|
+
id: section.id,
|
|
1247
|
+
name,
|
|
1248
|
+
category,
|
|
1249
|
+
package: section.package ?? null,
|
|
1250
|
+
type: section.kind ?? null,
|
|
1251
|
+
};
|
|
1252
|
+
});
|
|
1253
|
+
}
|
|
1254
|
+
|
|
1255
|
+
function normalizeToKebab(name) {
|
|
1256
|
+
const stripped = name.replace(/^[Tt]ui[-_]?/, '');
|
|
1257
|
+
|
|
1258
|
+
return stripped
|
|
1259
|
+
.replaceAll(/([a-z])([A-Z])/g, '$1-$2')
|
|
1260
|
+
.toLowerCase();
|
|
1261
|
+
}
|
|
1262
|
+
|
|
1263
|
+
function findSection(name, sections) {
|
|
1264
|
+
const stripped = name.replace(/^[Tt]ui[-_]?/, '');
|
|
1265
|
+
const kebab = normalizeToKebab(name);
|
|
1266
|
+
|
|
1267
|
+
const kebabParts = kebab.split('-').filter(Boolean);
|
|
1268
|
+
const lastWordCandidate =
|
|
1269
|
+
kebabParts.length > 1 ? (kebabParts[kebabParts.length - 1] ?? '') : '';
|
|
1270
|
+
const lastWord = GENERIC_SUFFIXES.has(lastWordCandidate) ? '' : lastWordCandidate;
|
|
1271
|
+
|
|
1272
|
+
const pascalCase = (stripped || name)
|
|
1273
|
+
.toLowerCase()
|
|
1274
|
+
.split(/[-_\s]+/)
|
|
1275
|
+
.filter(Boolean)
|
|
1276
|
+
.map((part) => `${part.charAt(0).toUpperCase()}${part.slice(1)}`)
|
|
1277
|
+
.join('');
|
|
1278
|
+
|
|
1279
|
+
const tuiVariant = pascalCase.startsWith('Tui') ? pascalCase : `Tui${pascalCase}`;
|
|
1280
|
+
|
|
1281
|
+
const variants = [
|
|
1282
|
+
name.toLowerCase(),
|
|
1283
|
+
stripped.toLowerCase(),
|
|
1284
|
+
kebab,
|
|
1285
|
+
lastWord,
|
|
1286
|
+
pascalCase.toLowerCase(),
|
|
1287
|
+
tuiVariant.toLowerCase(),
|
|
1288
|
+
].filter(Boolean);
|
|
1289
|
+
|
|
1290
|
+
const prepared = sections.map((section) => ({
|
|
1291
|
+
section,
|
|
1292
|
+
id: section.id.toLowerCase(),
|
|
1293
|
+
segment: section.id.split('/').pop()?.toLowerCase() ?? '',
|
|
1294
|
+
}));
|
|
1295
|
+
|
|
1296
|
+
for (const variant of variants) {
|
|
1297
|
+
const match = prepared.find((item) => item.id === variant);
|
|
1298
|
+
|
|
1299
|
+
if (match) {
|
|
1300
|
+
return match.section;
|
|
1301
|
+
}
|
|
1302
|
+
}
|
|
1303
|
+
|
|
1304
|
+
for (const variant of variants) {
|
|
1305
|
+
const match = prepared.find((item) => item.segment === variant);
|
|
1306
|
+
|
|
1307
|
+
if (match) {
|
|
1308
|
+
return match.section;
|
|
1309
|
+
}
|
|
1310
|
+
}
|
|
1311
|
+
|
|
1312
|
+
for (const variant of variants) {
|
|
1313
|
+
const match = prepared.find((item) => item.id.endsWith(`/${variant}`));
|
|
1314
|
+
|
|
1315
|
+
if (match) {
|
|
1316
|
+
return match.section;
|
|
1317
|
+
}
|
|
1318
|
+
}
|
|
1319
|
+
|
|
1320
|
+
for (const variant of variants) {
|
|
1321
|
+
const match = prepared.find((item) => item.id.includes(variant));
|
|
1322
|
+
|
|
1323
|
+
if (match) {
|
|
1324
|
+
return match.section;
|
|
1325
|
+
}
|
|
1326
|
+
}
|
|
1327
|
+
|
|
1328
|
+
return undefined;
|
|
1329
|
+
}
|
|
1330
|
+
|
|
1331
|
+
function suggestSections(query, sections) {
|
|
1332
|
+
const kebab = normalizeToKebab(query);
|
|
1333
|
+
|
|
1334
|
+
const parts = kebab
|
|
1335
|
+
.split('-')
|
|
1336
|
+
.filter((part) => !GENERIC_SUFFIXES.has(part) && part.length > 1);
|
|
1337
|
+
|
|
1338
|
+
const normalizedQuery = (parts.join('-') || kebab || query).toLowerCase();
|
|
1339
|
+
|
|
1340
|
+
const results = [];
|
|
1341
|
+
|
|
1342
|
+
for (const section of sections) {
|
|
1343
|
+
const idLower = section.id.toLowerCase();
|
|
1344
|
+
const matchIndex = idLower.indexOf(normalizedQuery);
|
|
1345
|
+
|
|
1346
|
+
if (matchIndex !== -1) {
|
|
1347
|
+
results.push({
|
|
1348
|
+
id: section.id,
|
|
1349
|
+
score:
|
|
1350
|
+
matchIndex * 10 + Math.abs(idLower.length - normalizedQuery.length),
|
|
1351
|
+
});
|
|
1352
|
+
}
|
|
1353
|
+
}
|
|
1354
|
+
|
|
1355
|
+
return results.sort((a, b) => a.score - b.score).map((item) => item.id);
|
|
1356
|
+
}
|
|
1357
|
+
|
|
1358
|
+
function extractContentSnippets(section) {
|
|
1359
|
+
const text = section.content || '';
|
|
1360
|
+
const trimmed = text.trim();
|
|
1361
|
+
|
|
1362
|
+
if (!trimmed) {
|
|
1363
|
+
return [];
|
|
1364
|
+
}
|
|
1365
|
+
|
|
1366
|
+
const cleaned = trimmed
|
|
1367
|
+
.split(/\r?\n/)
|
|
1368
|
+
.map((line) => line.replace(/^#{1,6}\s*/, ''))
|
|
1369
|
+
.join('\n')
|
|
1370
|
+
.trim();
|
|
1371
|
+
|
|
1372
|
+
return cleaned ? [cleaned] : [];
|
|
1373
|
+
}
|
|
1374
|
+
|
|
1375
|
+
function paginateText(text, {offset = 0, maxChars}) {
|
|
1376
|
+
const totalChars = text.length;
|
|
1377
|
+
const safeOffset = Math.max(0, Math.min(offset, totalChars));
|
|
1378
|
+
|
|
1379
|
+
if (!Number.isFinite(maxChars)) {
|
|
1380
|
+
return {
|
|
1381
|
+
content: text.slice(safeOffset),
|
|
1382
|
+
page: {
|
|
1383
|
+
offset: safeOffset,
|
|
1384
|
+
maxChars: null,
|
|
1385
|
+
returnedChars: totalChars - safeOffset,
|
|
1386
|
+
totalChars,
|
|
1387
|
+
truncated: false,
|
|
1388
|
+
nextOffset: null,
|
|
1389
|
+
},
|
|
1390
|
+
};
|
|
1391
|
+
}
|
|
1392
|
+
|
|
1393
|
+
const end = Math.min(safeOffset + maxChars, totalChars);
|
|
1394
|
+
|
|
1395
|
+
return {
|
|
1396
|
+
content: text.slice(safeOffset, end),
|
|
1397
|
+
page: {
|
|
1398
|
+
offset: safeOffset,
|
|
1399
|
+
maxChars,
|
|
1400
|
+
returnedChars: end - safeOffset,
|
|
1401
|
+
totalChars,
|
|
1402
|
+
truncated: end < totalChars,
|
|
1403
|
+
nextOffset: end < totalChars ? end : null,
|
|
1404
|
+
},
|
|
1405
|
+
};
|
|
1406
|
+
}
|
|
1407
|
+
|
|
1408
|
+
function validateExampleNames(names) {
|
|
1409
|
+
if (!names.length) {
|
|
1410
|
+
return {ok: false, error: 'At least one component name is required for example.'};
|
|
1411
|
+
}
|
|
1412
|
+
|
|
1413
|
+
for (const name of names) {
|
|
1414
|
+
if (name.length < 2) {
|
|
1415
|
+
return {
|
|
1416
|
+
ok: false,
|
|
1417
|
+
error: `Example name must have at least 2 characters: "${name}"`,
|
|
1418
|
+
};
|
|
1419
|
+
}
|
|
1420
|
+
}
|
|
1421
|
+
|
|
1422
|
+
return {ok: true};
|
|
1423
|
+
}
|
|
1424
|
+
|
|
1425
|
+
function handleOverview(index) {
|
|
1426
|
+
const headerInfo = index.overview
|
|
1427
|
+
? parseHeaderSections(index.overview)
|
|
1428
|
+
: {
|
|
1429
|
+
title: 'Taiga UI - Complete Documentation',
|
|
1430
|
+
sections: [],
|
|
1431
|
+
};
|
|
1432
|
+
|
|
1433
|
+
const mappedSections = headerInfo.sections.map((section) => {
|
|
1434
|
+
const sectionData = {
|
|
1435
|
+
title: section.title,
|
|
1436
|
+
criticalNotices: section.criticalNotices,
|
|
1437
|
+
subsections: section.subsections.map((subsection) => {
|
|
1438
|
+
const subsectionData = {
|
|
1439
|
+
title: subsection.title,
|
|
1440
|
+
content: subsection.content,
|
|
1441
|
+
};
|
|
1442
|
+
|
|
1443
|
+
if (subsection.sections && subsection.sections.length > 0) {
|
|
1444
|
+
subsectionData.sections = subsection.sections;
|
|
1445
|
+
}
|
|
1446
|
+
|
|
1447
|
+
if (subsection.items && subsection.items.length > 0) {
|
|
1448
|
+
subsectionData.items = subsection.items.map((item) => {
|
|
1449
|
+
const itemData = {
|
|
1450
|
+
title: item.title,
|
|
1451
|
+
content: item.content,
|
|
1452
|
+
};
|
|
1453
|
+
|
|
1454
|
+
if (item.code) {
|
|
1455
|
+
itemData.code = item.code;
|
|
1456
|
+
}
|
|
1457
|
+
|
|
1458
|
+
if (item.sections && item.sections.length > 0) {
|
|
1459
|
+
itemData.sections = item.sections;
|
|
1460
|
+
}
|
|
1461
|
+
|
|
1462
|
+
return itemData;
|
|
1463
|
+
});
|
|
1464
|
+
}
|
|
1465
|
+
|
|
1466
|
+
return subsectionData;
|
|
1467
|
+
}),
|
|
1468
|
+
};
|
|
1469
|
+
|
|
1470
|
+
if (section.description) {
|
|
1471
|
+
sectionData.description = section.description;
|
|
1472
|
+
}
|
|
1473
|
+
|
|
1474
|
+
return sectionData;
|
|
1475
|
+
});
|
|
1476
|
+
|
|
1477
|
+
return {
|
|
1478
|
+
title: headerInfo.title,
|
|
1479
|
+
sections: mappedSections,
|
|
1480
|
+
totalComponents: index.sections.length,
|
|
1481
|
+
sourceUrl: index.source.sourceUrl,
|
|
1482
|
+
};
|
|
1483
|
+
}
|
|
1484
|
+
|
|
1485
|
+
function handleList(index, query, options) {
|
|
1486
|
+
const items = constructComponentsList(index.sections, query);
|
|
1487
|
+
const offset = options.offset ?? 0;
|
|
1488
|
+
const limit = options.limit;
|
|
1489
|
+
|
|
1490
|
+
const returnedItems =
|
|
1491
|
+
typeof limit === 'number'
|
|
1492
|
+
? items.slice(offset, offset + limit)
|
|
1493
|
+
: items.slice(offset);
|
|
1494
|
+
|
|
1495
|
+
if (typeof limit !== 'number' && offset === 0) {
|
|
1496
|
+
return {items: returnedItems};
|
|
1497
|
+
}
|
|
1498
|
+
|
|
1499
|
+
return {
|
|
1500
|
+
items: returnedItems,
|
|
1501
|
+
total: items.length,
|
|
1502
|
+
returned: returnedItems.length,
|
|
1503
|
+
offset,
|
|
1504
|
+
limit: limit ?? null,
|
|
1505
|
+
};
|
|
1506
|
+
}
|
|
1507
|
+
|
|
1508
|
+
function handleExample(index, names, options) {
|
|
1509
|
+
const results = [];
|
|
1510
|
+
let matched = 0;
|
|
1511
|
+
|
|
1512
|
+
for (const queryName of names) {
|
|
1513
|
+
const section = findSection(queryName, index.sections);
|
|
1514
|
+
|
|
1515
|
+
if (!section) {
|
|
1516
|
+
results.push({
|
|
1517
|
+
query: queryName,
|
|
1518
|
+
suggestions: suggestSections(queryName, index.sections),
|
|
1519
|
+
});
|
|
1520
|
+
|
|
1521
|
+
continue;
|
|
1522
|
+
}
|
|
1523
|
+
|
|
1524
|
+
matched += 1;
|
|
1525
|
+
|
|
1526
|
+
const snippets = extractContentSnippets(section);
|
|
1527
|
+
const found = {
|
|
1528
|
+
query: queryName,
|
|
1529
|
+
id: section.id,
|
|
1530
|
+
package: section.package ?? null,
|
|
1531
|
+
type: section.kind ?? null,
|
|
1532
|
+
};
|
|
1533
|
+
|
|
1534
|
+
if (snippets[0]) {
|
|
1535
|
+
if (typeof options.maxChars === 'number') {
|
|
1536
|
+
const paginated = paginateText(snippets[0], {
|
|
1537
|
+
offset: options.offset ?? 0,
|
|
1538
|
+
maxChars: options.maxChars,
|
|
1539
|
+
});
|
|
1540
|
+
|
|
1541
|
+
found.content = [paginated.content];
|
|
1542
|
+
found.page = paginated.page;
|
|
1543
|
+
} else {
|
|
1544
|
+
found.content = snippets;
|
|
1545
|
+
}
|
|
1546
|
+
}
|
|
1547
|
+
|
|
1548
|
+
results.push(found);
|
|
1549
|
+
}
|
|
1550
|
+
|
|
1551
|
+
return {
|
|
1552
|
+
results,
|
|
1553
|
+
matched,
|
|
1554
|
+
};
|
|
1555
|
+
}
|
|
1556
|
+
|
|
1557
|
+
function handleMigration(index) {
|
|
1558
|
+
if (!index.migrationGuide?.trim()) {
|
|
1559
|
+
return {
|
|
1560
|
+
error: 'Migration Guide is not available. Ensure the source file contains the Migration Guide section.',
|
|
1561
|
+
};
|
|
1562
|
+
}
|
|
1563
|
+
|
|
1564
|
+
const parsed = parseMigrationGuide(index.migrationGuide);
|
|
1565
|
+
|
|
1566
|
+
return {
|
|
1567
|
+
title: parsed.title,
|
|
1568
|
+
introduction: parsed.introduction,
|
|
1569
|
+
sections: parsed.sections.map((section) => {
|
|
1570
|
+
const sectionData = {
|
|
1571
|
+
title: section.title,
|
|
1572
|
+
content: section.content,
|
|
1573
|
+
};
|
|
1574
|
+
|
|
1575
|
+
if (section.codeBlocks && section.codeBlocks.length > 0) {
|
|
1576
|
+
sectionData.codeBlocks = section.codeBlocks;
|
|
1577
|
+
}
|
|
1578
|
+
|
|
1579
|
+
if (section.subsections && section.subsections.length > 0) {
|
|
1580
|
+
sectionData.subsections = section.subsections.map((subsection) => {
|
|
1581
|
+
const subsectionData = {
|
|
1582
|
+
title: subsection.title,
|
|
1583
|
+
content: subsection.content,
|
|
1584
|
+
};
|
|
1585
|
+
|
|
1586
|
+
if (subsection.codeBlocks && subsection.codeBlocks.length > 0) {
|
|
1587
|
+
subsectionData.codeBlocks = subsection.codeBlocks;
|
|
1588
|
+
}
|
|
1589
|
+
|
|
1590
|
+
return subsectionData;
|
|
1591
|
+
});
|
|
1592
|
+
}
|
|
1593
|
+
|
|
1594
|
+
return sectionData;
|
|
1595
|
+
}),
|
|
1596
|
+
};
|
|
1597
|
+
}
|
|
1598
|
+
|
|
1599
|
+
async function emitResult(payload, options) {
|
|
1600
|
+
const pretty = Boolean(options.pretty);
|
|
1601
|
+
const serialized = JSON.stringify(payload, null, pretty ? 2 : 0);
|
|
1602
|
+
|
|
1603
|
+
if (options.output) {
|
|
1604
|
+
const outputPath = path.resolve(process.cwd(), options.output);
|
|
1605
|
+
|
|
1606
|
+
if (!options.force) {
|
|
1607
|
+
try {
|
|
1608
|
+
await fs.access(outputPath);
|
|
1609
|
+
await failWithCode(
|
|
1610
|
+
5,
|
|
1611
|
+
{
|
|
1612
|
+
error: `Output file already exists. Use --force to overwrite: ${outputPath}`,
|
|
1613
|
+
},
|
|
1614
|
+
options,
|
|
1615
|
+
);
|
|
1616
|
+
return;
|
|
1617
|
+
} catch {
|
|
1618
|
+
// Continue.
|
|
1619
|
+
}
|
|
1620
|
+
}
|
|
1621
|
+
|
|
1622
|
+
try {
|
|
1623
|
+
await fs.mkdir(path.dirname(outputPath), {recursive: true});
|
|
1624
|
+
await fs.writeFile(outputPath, serialized, 'utf8');
|
|
1625
|
+
|
|
1626
|
+
const summary = {
|
|
1627
|
+
output: outputPath,
|
|
1628
|
+
bytes: Buffer.from(serialized, 'utf8').byteLength,
|
|
1629
|
+
pretty,
|
|
1630
|
+
};
|
|
1631
|
+
|
|
1632
|
+
console.log(JSON.stringify(summary, null, pretty ? 2 : 0));
|
|
1633
|
+
return;
|
|
1634
|
+
} catch (error) {
|
|
1635
|
+
await failWithCode(
|
|
1636
|
+
5,
|
|
1637
|
+
{
|
|
1638
|
+
error: `Failed to write output file ${outputPath}: ${
|
|
1639
|
+
error instanceof Error ? error.message : String(error)
|
|
1640
|
+
}`,
|
|
1641
|
+
},
|
|
1642
|
+
options,
|
|
1643
|
+
);
|
|
1644
|
+
}
|
|
1645
|
+
|
|
1646
|
+
return;
|
|
1647
|
+
}
|
|
1648
|
+
|
|
1649
|
+
console.log(serialized);
|
|
1650
|
+
}
|
|
1651
|
+
|
|
1652
|
+
async function failWithCode(code, payload, options = {}, context = {}) {
|
|
1653
|
+
if (!context.noStdout && payload !== undefined) {
|
|
1654
|
+
const pretty = Boolean(options.pretty);
|
|
1655
|
+
console.log(JSON.stringify(payload, null, pretty ? 2 : 0));
|
|
1656
|
+
}
|
|
1657
|
+
|
|
1658
|
+
const diagnostic = context.diagnostic ?? payload?.error;
|
|
1659
|
+
|
|
1660
|
+
if (diagnostic) {
|
|
1661
|
+
console.error(diagnostic);
|
|
1662
|
+
}
|
|
1663
|
+
|
|
1664
|
+
if (!context.noStdout && code === 2 && context.helpHint) {
|
|
1665
|
+
console.error('Try: node scripts/taiga-ui-docs.mjs --help');
|
|
1666
|
+
}
|
|
1667
|
+
|
|
1668
|
+
process.exit(code);
|
|
1669
|
+
}
|
|
1670
|
+
|
|
1671
|
+
function printHelp() {
|
|
1672
|
+
console.log(`Usage:
|
|
1673
|
+
node scripts/taiga-ui-docs.mjs overview [options]
|
|
1674
|
+
node scripts/taiga-ui-docs.mjs list [query] [options]
|
|
1675
|
+
node scripts/taiga-ui-docs.mjs example <name...> [options]
|
|
1676
|
+
node scripts/taiga-ui-docs.mjs migration [options]
|
|
1677
|
+
|
|
1678
|
+
Description:
|
|
1679
|
+
Fetches/parses Taiga UI llms-full.txt and returns JSON equivalent to the Taiga UI MCP docs tools.
|
|
1680
|
+
|
|
1681
|
+
Options:
|
|
1682
|
+
--source-url <url> Docs source URL. Also supports --source-url=<url>.
|
|
1683
|
+
--source-file <path> Read docs from local file.
|
|
1684
|
+
--refresh Force remote refetch.
|
|
1685
|
+
--no-cache Disable cache for this run.
|
|
1686
|
+
--cache-dir <path> Override cache directory.
|
|
1687
|
+
--ttl-ms <n> Cache TTL in ms. Default: 21600000.
|
|
1688
|
+
--limit <n> Limit list results. Default: no limit.
|
|
1689
|
+
--offset <n> Offset for list/content pagination. Default: 0.
|
|
1690
|
+
--max-chars <n> Max chars returned for large example content. Default: full content.
|
|
1691
|
+
--output <file> Write full JSON to file; stdout contains JSON file summary.
|
|
1692
|
+
--force Overwrite --output file if it exists.
|
|
1693
|
+
--pretty Pretty-print JSON.
|
|
1694
|
+
--help Show help.
|
|
1695
|
+
|
|
1696
|
+
Environment:
|
|
1697
|
+
TAIGA_UI_DOCS_SOURCE_FILE Local docs source file.
|
|
1698
|
+
TAIGA_UI_DOCS_SOURCE_URL Docs source URL.
|
|
1699
|
+
SOURCE_URL Docs source URL, compatible with @taiga-ui/mcp.
|
|
1700
|
+
|
|
1701
|
+
Exit codes:
|
|
1702
|
+
0 success, including example queries with zero matches
|
|
1703
|
+
2 invalid arguments
|
|
1704
|
+
3 source fetch/read/cache error
|
|
1705
|
+
4 requested documentation section is unavailable, e.g. missing migration guide
|
|
1706
|
+
5 output file error
|
|
1707
|
+
|
|
1708
|
+
Examples:
|
|
1709
|
+
node scripts/taiga-ui-docs.mjs overview --pretty
|
|
1710
|
+
node scripts/taiga-ui-docs.mjs list button --limit 100 --pretty
|
|
1711
|
+
node scripts/taiga-ui-docs.mjs example Button --max-chars 24000 --pretty
|
|
1712
|
+
node scripts/taiga-ui-docs.mjs example TuiInput --offset 24000 --max-chars 24000 --pretty
|
|
1713
|
+
node scripts/taiga-ui-docs.mjs migration --pretty`);
|
|
1714
|
+
}
|
|
1715
|
+
|
|
1716
|
+
main().catch((error) => {
|
|
1717
|
+
console.error(error?.stack || String(error));
|
|
1718
|
+
process.exit(1);
|
|
1719
|
+
});
|