skrypt-ai 0.3.4 → 0.4.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.
Files changed (95) hide show
  1. package/README.md +1 -1
  2. package/dist/auth/index.d.ts +0 -1
  3. package/dist/auth/index.js +3 -5
  4. package/dist/autofix/index.js +15 -3
  5. package/dist/cli.js +19 -4
  6. package/dist/commands/check-links.js +164 -174
  7. package/dist/commands/deploy.js +5 -2
  8. package/dist/commands/generate.js +206 -199
  9. package/dist/commands/i18n.js +3 -20
  10. package/dist/commands/init.js +47 -40
  11. package/dist/commands/lint.js +3 -20
  12. package/dist/commands/mcp.js +125 -122
  13. package/dist/commands/monitor.js +125 -108
  14. package/dist/commands/review-pr.js +1 -1
  15. package/dist/commands/sdk.js +1 -1
  16. package/dist/config/loader.js +21 -2
  17. package/dist/generator/organizer.d.ts +3 -0
  18. package/dist/generator/organizer.js +4 -9
  19. package/dist/generator/writer.js +2 -10
  20. package/dist/github/pr-comments.js +21 -8
  21. package/dist/plugins/index.js +1 -0
  22. package/dist/scanner/index.js +8 -2
  23. package/dist/template/docs.json +2 -1
  24. package/dist/template/next.config.mjs +2 -1
  25. package/dist/template/package.json +17 -15
  26. package/dist/template/public/favicon.svg +4 -0
  27. package/dist/template/public/search-index.json +1 -1
  28. package/dist/template/scripts/build-search-index.mjs +120 -25
  29. package/dist/template/src/app/api/chat/route.ts +11 -3
  30. package/dist/template/src/app/docs/README.md +28 -0
  31. package/dist/template/src/app/docs/[...slug]/page.tsx +139 -16
  32. package/dist/template/src/app/docs/auth/page.mdx +589 -0
  33. package/dist/template/src/app/docs/autofix/page.mdx +624 -0
  34. package/dist/template/src/app/docs/cli/page.mdx +217 -0
  35. package/dist/template/src/app/docs/config/page.mdx +428 -0
  36. package/dist/template/src/app/docs/configuration/page.mdx +86 -0
  37. package/dist/template/src/app/docs/deployment/page.mdx +112 -0
  38. package/dist/template/src/app/docs/error.tsx +20 -0
  39. package/dist/template/src/app/docs/generator/generator.md +504 -0
  40. package/dist/template/src/app/docs/generator/organizer.md +779 -0
  41. package/dist/template/src/app/docs/generator/page.mdx +613 -0
  42. package/dist/template/src/app/docs/github/page.mdx +502 -0
  43. package/dist/template/src/app/docs/llm/anthropic-client.md +549 -0
  44. package/dist/template/src/app/docs/llm/index.md +471 -0
  45. package/dist/template/src/app/docs/llm/page.mdx +428 -0
  46. package/dist/template/src/app/docs/llms-full.md +256 -0
  47. package/dist/template/src/app/docs/llms.txt +2971 -0
  48. package/dist/template/src/app/docs/not-found.tsx +23 -0
  49. package/dist/template/src/app/docs/page.mdx +0 -3
  50. package/dist/template/src/app/docs/plugins/page.mdx +1793 -0
  51. package/dist/template/src/app/docs/pro/page.mdx +121 -0
  52. package/dist/template/src/app/docs/quickstart/page.mdx +93 -0
  53. package/dist/template/src/app/docs/scanner/content-type.md +599 -0
  54. package/dist/template/src/app/docs/scanner/index.md +212 -0
  55. package/dist/template/src/app/docs/scanner/page.mdx +307 -0
  56. package/dist/template/src/app/docs/scanner/python.md +469 -0
  57. package/dist/template/src/app/docs/scanner/python_parser.md +1056 -0
  58. package/dist/template/src/app/docs/scanner/rust.md +325 -0
  59. package/dist/template/src/app/docs/scanner/typescript.md +201 -0
  60. package/dist/template/src/app/error.tsx +3 -3
  61. package/dist/template/src/app/icon.tsx +29 -0
  62. package/dist/template/src/app/layout.tsx +42 -0
  63. package/dist/template/src/app/not-found.tsx +35 -0
  64. package/dist/template/src/app/page.tsx +62 -28
  65. package/dist/template/src/components/ai-chat.tsx +26 -21
  66. package/dist/template/src/components/breadcrumbs.tsx +46 -2
  67. package/dist/template/src/components/copy-button.tsx +17 -3
  68. package/dist/template/src/components/docs-layout.tsx +142 -8
  69. package/dist/template/src/components/feedback.tsx +4 -2
  70. package/dist/template/src/components/footer.tsx +42 -0
  71. package/dist/template/src/components/header.tsx +29 -5
  72. package/dist/template/src/components/mdx/accordion.tsx +7 -6
  73. package/dist/template/src/components/mdx/card.tsx +19 -7
  74. package/dist/template/src/components/mdx/code-block.tsx +17 -3
  75. package/dist/template/src/components/mdx/code-group.tsx +65 -18
  76. package/dist/template/src/components/mdx/code-playground.tsx +3 -0
  77. package/dist/template/src/components/mdx/go-playground.tsx +3 -0
  78. package/dist/template/src/components/mdx/highlighted-code.tsx +171 -76
  79. package/dist/template/src/components/mdx/python-playground.tsx +2 -0
  80. package/dist/template/src/components/mdx/tabs.tsx +74 -6
  81. package/dist/template/src/components/page-header.tsx +19 -0
  82. package/dist/template/src/components/scroll-to-top.tsx +33 -0
  83. package/dist/template/src/components/search-dialog.tsx +206 -52
  84. package/dist/template/src/components/sidebar.tsx +136 -77
  85. package/dist/template/src/components/table-of-contents.tsx +23 -7
  86. package/dist/template/src/lib/highlight.ts +90 -31
  87. package/dist/template/src/lib/search.ts +14 -4
  88. package/dist/template/src/lib/theme-utils.ts +140 -0
  89. package/dist/template/src/styles/globals.css +307 -166
  90. package/dist/template/src/types/remark-gfm.d.ts +2 -0
  91. package/dist/utils/files.d.ts +9 -0
  92. package/dist/utils/files.js +33 -0
  93. package/dist/utils/validation.d.ts +4 -0
  94. package/dist/utils/validation.js +38 -0
  95. package/package.json +1 -4
@@ -102,219 +102,226 @@ export const generateCommand = new Command('generate')
102
102
  .option('--llms-txt', 'Generate llms.txt for Answer Engine Optimization (AEO)')
103
103
  .option('--project-name <name>', 'Project name for llms.txt header')
104
104
  .action(async (source, options) => {
105
- const startTime = Date.now();
106
- // Load config (file or defaults)
107
- const config = loadConfig(options.config);
108
- // CLI flags override config
109
- if (source)
110
- config.source.path = source;
111
- if (options.output)
112
- config.output.path = options.output;
113
- if (options.provider) {
114
- config.llm.provider = options.provider;
115
- // Use provider's default model unless explicitly specified
116
- if (!options.model) {
117
- config.llm.model = DEFAULT_MODELS[config.llm.provider];
105
+ try {
106
+ const startTime = Date.now();
107
+ // Load config (file or defaults)
108
+ const config = loadConfig(options.config);
109
+ // CLI flags override config
110
+ if (source)
111
+ config.source.path = source;
112
+ if (options.output)
113
+ config.output.path = options.output;
114
+ if (options.provider) {
115
+ config.llm.provider = options.provider;
116
+ // Use provider's default model unless explicitly specified
117
+ if (!options.model) {
118
+ config.llm.model = DEFAULT_MODELS[config.llm.provider];
119
+ }
118
120
  }
119
- }
120
- if (options.model)
121
- config.llm.model = options.model;
122
- if (options.baseUrl)
123
- config.llm.baseUrl = options.baseUrl;
124
- // Validate
125
- const errors = validateConfig(config);
126
- if (errors.length > 0) {
127
- console.error('Config errors:');
128
- errors.forEach(e => console.error(` - ${e}`));
129
- process.exit(1);
130
- }
131
- // Check for API key (not needed for Ollama or dry-run)
132
- if (!options.dryRun) {
133
- const { ok, envKey } = checkApiKey(config.llm.provider);
134
- if (!ok && envKey) {
135
- console.error(`Error: ${envKey} environment variable required for ${config.llm.provider}`);
121
+ if (options.model)
122
+ config.llm.model = options.model;
123
+ if (options.baseUrl)
124
+ config.llm.baseUrl = options.baseUrl;
125
+ // Validate
126
+ const errors = validateConfig(config);
127
+ if (errors.length > 0) {
128
+ console.error('Config errors:');
129
+ errors.forEach(e => console.error(` - ${e}`));
136
130
  process.exit(1);
137
131
  }
138
- }
139
- console.log('skrypt generate');
140
- console.log(` source: ${config.source.path}`);
141
- console.log(` output: ${config.output.path}`);
142
- console.log(` provider: ${config.llm.provider}`);
143
- console.log(` model: ${config.llm.model}`);
144
- if (config.llm.baseUrl) {
145
- console.log(` base url: ${config.llm.baseUrl}`);
146
- }
147
- console.log('');
148
- // Check source exists
149
- const sourcePath = resolve(config.source.path);
150
- if (!existsSync(sourcePath)) {
151
- console.error(`Error: Source directory not found: ${sourcePath}`);
152
- process.exit(1);
153
- }
154
- // Step 1: Scan source code
155
- console.log('Step 1: Scanning source code...');
156
- const scanResult = await scanDirectory(sourcePath, {
157
- include: config.source.include,
158
- exclude: config.source.exclude,
159
- onProgress: (current, total, file) => {
160
- process.stdout.write(`\r [${current}/${total}] ${file.slice(-50).padStart(50)}`);
132
+ // Check for API key (not needed for Ollama or dry-run)
133
+ if (!options.dryRun) {
134
+ const { ok, envKey } = checkApiKey(config.llm.provider);
135
+ if (!ok && envKey) {
136
+ console.error(`Error: ${envKey} environment variable required for ${config.llm.provider}`);
137
+ process.exit(1);
138
+ }
161
139
  }
162
- });
163
- console.log('');
164
- if (scanResult.errors.length > 0) {
165
- console.log('\n Scan warnings:');
166
- scanResult.errors.slice(0, 5).forEach(e => console.log(` - ${e}`));
167
- if (scanResult.errors.length > 5) {
168
- console.log(` ... and ${scanResult.errors.length - 5} more`);
140
+ console.log('skrypt generate');
141
+ console.log(` source: ${config.source.path}`);
142
+ console.log(` output: ${config.output.path}`);
143
+ console.log(` provider: ${config.llm.provider}`);
144
+ console.log(` model: ${config.llm.model}`);
145
+ if (config.llm.baseUrl) {
146
+ console.log(` base url: ${config.llm.baseUrl}`);
169
147
  }
170
- }
171
- console.log(`\n Found ${scanResult.totalElements} API elements in ${scanResult.files.length} files`);
172
- if (scanResult.totalElements === 0) {
173
- console.log(' No API elements found. Nothing to generate.');
174
- process.exit(0);
175
- }
176
- // Collect all elements
177
- let allElements = [];
178
- for (const file of scanResult.files) {
179
- for (const el of file.elements) {
180
- allElements.push(el);
148
+ console.log('');
149
+ // Check source exists
150
+ const sourcePath = resolve(config.source.path);
151
+ if (!existsSync(sourcePath)) {
152
+ console.error(`Error: Source directory not found: ${sourcePath}`);
153
+ process.exit(1);
181
154
  }
182
- }
183
- // Apply privacy filters
184
- const initialCount = allElements.length;
185
- // 1. --public-only: filter to exported/public APIs only
186
- if (options.publicOnly) {
187
- allElements = allElements.filter(el => el.isExported === true || el.isPublic === true);
188
- console.log(` --public-only: filtered to ${allElements.length} exported APIs`);
189
- }
190
- // 2. Load .skryptignore patterns
191
- const ignorePatterns = readIgnorePatterns(sourcePath);
192
- if (ignorePatterns.length > 0) {
193
- console.log(` .skryptignore: loaded ${ignorePatterns.length} patterns`);
194
- }
195
- // 3. Combine with --exclude patterns
196
- const excludePatterns = [...ignorePatterns, ...(options.exclude || [])];
197
- if (excludePatterns.length > 0) {
198
- allElements = allElements.filter(el => !shouldExcludeElement(el, excludePatterns));
199
- if (options.exclude?.length) {
200
- console.log(` --exclude: applied ${options.exclude.length} additional patterns`);
155
+ // Step 1: Scan source code
156
+ console.log('Step 1: Scanning source code...');
157
+ const scanResult = await scanDirectory(sourcePath, {
158
+ include: config.source.include,
159
+ exclude: config.source.exclude,
160
+ onProgress: (current, total, file) => {
161
+ process.stdout.write(`\r [${current}/${total}] ${file.slice(-50).padStart(50)}`);
162
+ }
163
+ });
164
+ console.log('');
165
+ if (scanResult.errors.length > 0) {
166
+ console.log('\n Scan warnings:');
167
+ scanResult.errors.slice(0, 5).forEach(e => console.log(` - ${e}`));
168
+ if (scanResult.errors.length > 5) {
169
+ console.log(` ... and ${scanResult.errors.length - 5} more`);
170
+ }
201
171
  }
202
- }
203
- if (initialCount !== allElements.length) {
204
- console.log(` Filtered: ${initialCount} -> ${allElements.length} elements`);
205
- }
206
- // Show summary by kind
207
- const byKind = {};
208
- for (const el of allElements) {
209
- byKind[el.kind] = (byKind[el.kind] || 0) + 1;
210
- }
211
- const pluralize = (word, count) => {
212
- if (count === 1)
213
- return word;
214
- if (word === 'class')
215
- return 'classes';
216
- return word + 's';
217
- };
218
- console.log(' ' + Object.entries(byKind).map(([k, v]) => `${v} ${pluralize(k, v)}`).join(', '));
219
- // Dry run - stop here
220
- if (options.dryRun) {
221
- console.log('\n[dry run - stopping before generation]');
222
- process.exit(0);
223
- }
224
- // Step 2: Generate docs
225
- console.log('\nStep 2: Generating documentation...');
226
- const client = createLLMClient({
227
- provider: config.llm.provider,
228
- model: config.llm.model,
229
- baseUrl: config.llm.baseUrl
230
- });
231
- let lastElement = '';
232
- const multiLanguage = options.multiLang ?? false;
233
- if (multiLanguage) {
234
- console.log(' mode: multi-language (TypeScript + Python)');
235
- }
236
- const docs = await generateForElements(allElements, client, {
237
- multiLanguage,
238
- onProgress: (progress) => {
239
- if (progress.element !== lastElement) {
240
- if (lastElement)
241
- console.log('');
242
- lastElement = progress.element;
172
+ console.log(`\n Found ${scanResult.totalElements} API elements in ${scanResult.files.length} files`);
173
+ if (scanResult.totalElements === 0) {
174
+ console.log(' No API elements found. Nothing to generate.');
175
+ process.exit(0);
176
+ }
177
+ // Collect all elements
178
+ let allElements = [];
179
+ for (const file of scanResult.files) {
180
+ for (const el of file.elements) {
181
+ allElements.push(el);
243
182
  }
244
- process.stdout.write(`\r [${progress.current}/${progress.total}] ${progress.element}: ${progress.status}`.padEnd(80));
245
183
  }
246
- });
247
- console.log('\n');
248
- // Step 3: Write output
249
- console.log('Step 3: Writing documentation...');
250
- const outputPath = resolve(config.output.path);
251
- let filesWritten;
252
- let totalDocs;
253
- if (options.byTopic) {
254
- console.log(' mode: by-topic (grouped by concept)');
255
- const result = await writeDocsByTopic(docs, outputPath);
256
- filesWritten = result.filesWritten;
257
- totalDocs = result.totalDocs;
258
- console.log(` topics: ${result.topics.map(t => t.name).join(', ')}`);
259
- }
260
- else {
261
- // Default: file-based output
262
- const fileResults = groupDocsByFile(docs);
263
- const result = await writeDocsToDirectory(fileResults, outputPath, sourcePath);
264
- filesWritten = result.filesWritten;
265
- totalDocs = result.totalDocs;
266
- }
267
- const errorCount = docs.filter(d => d.error).length;
268
- const duration = Math.round((Date.now() - startTime) / 1000);
269
- console.log(`\n Wrote ${filesWritten} documentation files to ${outputPath}`);
270
- // Copy OpenAPI spec (provided or auto-detected)
271
- let specPath = options.openapi ? resolve(options.openapi) : null;
272
- // Auto-detect if not provided
273
- if (!specPath) {
274
- const detected = findOpenAPISpec(sourcePath);
275
- if (detected) {
276
- specPath = detected;
277
- console.log(`\n Auto-detected OpenAPI spec: ${basename(detected)}`);
184
+ // Apply privacy filters
185
+ const initialCount = allElements.length;
186
+ // 1. --public-only: filter to exported/public APIs only
187
+ if (options.publicOnly) {
188
+ allElements = allElements.filter(el => el.isExported === true || el.isPublic === true);
189
+ console.log(` --public-only: filtered to ${allElements.length} exported APIs`);
278
190
  }
279
- }
280
- if (specPath) {
281
- if (existsSync(specPath)) {
282
- const specFilename = basename(specPath);
283
- const contentDir = dirname(outputPath);
284
- const destPath = resolve(contentDir, specFilename);
285
- mkdirSync(dirname(destPath), { recursive: true });
286
- copyFileSync(specPath, destPath);
287
- console.log(` Copied OpenAPI spec: ${specFilename} -> ${destPath}`);
288
- console.log(' API Playground will be available at /reference');
191
+ // 2. Load .skryptignore patterns
192
+ const ignorePatterns = readIgnorePatterns(sourcePath);
193
+ if (ignorePatterns.length > 0) {
194
+ console.log(` .skryptignore: loaded ${ignorePatterns.length} patterns`);
289
195
  }
290
- else if (options.openapi) {
291
- console.log(`\n Warning: OpenAPI spec not found: ${specPath}`);
196
+ // 3. Combine with --exclude patterns
197
+ const excludePatterns = [...ignorePatterns, ...(options.exclude || [])];
198
+ if (excludePatterns.length > 0) {
199
+ allElements = allElements.filter(el => !shouldExcludeElement(el, excludePatterns));
200
+ if (options.exclude?.length) {
201
+ console.log(` --exclude: applied ${options.exclude.length} additional patterns`);
202
+ }
292
203
  }
293
- }
294
- // Generate llms.txt for AEO (Answer Engine Optimization)
295
- if (options.llmsTxt) {
296
- await writeLlmsTxt(docs, outputPath, {
297
- projectName: options.projectName,
298
- description: `API documentation for ${options.projectName || basename(sourcePath)}`
204
+ if (initialCount !== allElements.length) {
205
+ console.log(` Filtered: ${initialCount} -> ${allElements.length} elements`);
206
+ }
207
+ // Show summary by kind
208
+ const byKind = {};
209
+ for (const el of allElements) {
210
+ byKind[el.kind] = (byKind[el.kind] || 0) + 1;
211
+ }
212
+ const pluralize = (word, count) => {
213
+ if (count === 1)
214
+ return word;
215
+ if (word === 'class')
216
+ return 'classes';
217
+ return word + 's';
218
+ };
219
+ console.log(' ' + Object.entries(byKind).map(([k, v]) => `${v} ${pluralize(k, v)}`).join(', '));
220
+ // Dry run - stop here
221
+ if (options.dryRun) {
222
+ console.log('\n[dry run - stopping before generation]');
223
+ process.exit(0);
224
+ }
225
+ // Step 2: Generate docs
226
+ console.log('\nStep 2: Generating documentation...');
227
+ const client = createLLMClient({
228
+ provider: config.llm.provider,
229
+ model: config.llm.model,
230
+ baseUrl: config.llm.baseUrl
299
231
  });
300
- console.log(`\n Generated llms.txt and llms-full.md for AEO`);
301
- }
302
- console.log('\n=== Summary ===');
303
- console.log(` Total elements: ${totalDocs}`);
304
- console.log(` Generated: ${totalDocs - errorCount}`);
305
- if (errorCount > 0) {
306
- console.log(` Errors: ${errorCount}`);
307
- }
308
- console.log(` Duration: ${duration}s`);
309
- console.log(` Output: ${outputPath}`);
310
- if (errorCount > 0) {
311
- console.log('\n Elements with errors:');
312
- docs.filter(d => d.error).slice(0, 10).forEach(d => {
313
- console.log(` - ${d.element.name}: ${d.error?.slice(0, 50)}`);
232
+ let lastElement = '';
233
+ const multiLanguage = options.multiLang ?? false;
234
+ if (multiLanguage) {
235
+ console.log(' mode: multi-language (TypeScript + Python)');
236
+ }
237
+ const docs = await generateForElements(allElements, client, {
238
+ multiLanguage,
239
+ onProgress: (progress) => {
240
+ if (progress.element !== lastElement) {
241
+ if (lastElement)
242
+ console.log('');
243
+ lastElement = progress.element;
244
+ }
245
+ process.stdout.write(`\r [${progress.current}/${progress.total}] ${progress.element}: ${progress.status}`.padEnd(80));
246
+ }
314
247
  });
315
- if (errorCount > 10) {
316
- console.log(` ... and ${errorCount - 10} more`);
248
+ console.log('\n');
249
+ // Step 3: Write output
250
+ console.log('Step 3: Writing documentation...');
251
+ const outputPath = resolve(config.output.path);
252
+ let filesWritten;
253
+ let totalDocs;
254
+ if (options.byTopic) {
255
+ console.log(' mode: by-topic (grouped by concept)');
256
+ const result = await writeDocsByTopic(docs, outputPath);
257
+ filesWritten = result.filesWritten;
258
+ totalDocs = result.totalDocs;
259
+ console.log(` topics: ${result.topics.map(t => t.name).join(', ')}`);
260
+ }
261
+ else {
262
+ // Default: file-based output
263
+ const fileResults = groupDocsByFile(docs);
264
+ const result = await writeDocsToDirectory(fileResults, outputPath, sourcePath);
265
+ filesWritten = result.filesWritten;
266
+ totalDocs = result.totalDocs;
317
267
  }
268
+ const errorCount = docs.filter(d => d.error).length;
269
+ const duration = Math.round((Date.now() - startTime) / 1000);
270
+ console.log(`\n Wrote ${filesWritten} documentation files to ${outputPath}`);
271
+ // Copy OpenAPI spec (provided or auto-detected)
272
+ let specPath = options.openapi ? resolve(options.openapi) : null;
273
+ // Auto-detect if not provided
274
+ if (!specPath) {
275
+ const detected = findOpenAPISpec(sourcePath);
276
+ if (detected) {
277
+ specPath = detected;
278
+ console.log(`\n Auto-detected OpenAPI spec: ${basename(detected)}`);
279
+ }
280
+ }
281
+ if (specPath) {
282
+ if (existsSync(specPath)) {
283
+ const specFilename = basename(specPath);
284
+ const contentDir = dirname(outputPath);
285
+ const destPath = resolve(contentDir, specFilename);
286
+ mkdirSync(dirname(destPath), { recursive: true });
287
+ copyFileSync(specPath, destPath);
288
+ console.log(` Copied OpenAPI spec: ${specFilename} -> ${destPath}`);
289
+ console.log(' API Playground will be available at /reference');
290
+ }
291
+ else if (options.openapi) {
292
+ console.log(`\n Warning: OpenAPI spec not found: ${specPath}`);
293
+ }
294
+ }
295
+ // Generate llms.txt for AEO (Answer Engine Optimization)
296
+ if (options.llmsTxt) {
297
+ await writeLlmsTxt(docs, outputPath, {
298
+ projectName: options.projectName,
299
+ description: `API documentation for ${options.projectName || basename(sourcePath)}`
300
+ });
301
+ console.log(`\n Generated llms.txt and llms-full.md for AEO`);
302
+ }
303
+ console.log('\n=== Summary ===');
304
+ console.log(` Total elements: ${totalDocs}`);
305
+ console.log(` Generated: ${totalDocs - errorCount}`);
306
+ if (errorCount > 0) {
307
+ console.log(` Errors: ${errorCount}`);
308
+ }
309
+ console.log(` Duration: ${duration}s`);
310
+ console.log(` Output: ${outputPath}`);
311
+ if (errorCount > 0) {
312
+ console.log('\n Elements with errors:');
313
+ docs.filter(d => d.error).slice(0, 10).forEach(d => {
314
+ console.log(` - ${d.element.name}: ${d.error?.slice(0, 50)}`);
315
+ });
316
+ if (errorCount > 10) {
317
+ console.log(` ... and ${errorCount - 10} more`);
318
+ }
319
+ }
320
+ console.log('\nDone!');
321
+ }
322
+ catch (err) {
323
+ const message = err instanceof Error ? err.message : String(err);
324
+ console.error(`Error: ${message}`);
325
+ process.exit(1);
318
326
  }
319
- console.log('\nDone!');
320
327
  });
@@ -1,6 +1,7 @@
1
1
  import { Command } from 'commander';
2
- import { existsSync, readFileSync, writeFileSync, mkdirSync, readdirSync, statSync } from 'fs';
3
- import { resolve, join, extname, relative } from 'path';
2
+ import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';
3
+ import { resolve, join, relative } from 'path';
4
+ import { findMdxFiles } from '../utils/files.js';
4
5
  const CONFIG_FILE = 'skrypt.i18n.json';
5
6
  function loadI18nConfig(docsPath) {
6
7
  const configPath = join(docsPath, CONFIG_FILE);
@@ -13,24 +14,6 @@ function saveI18nConfig(docsPath, config) {
13
14
  const configPath = join(docsPath, CONFIG_FILE);
14
15
  writeFileSync(configPath, JSON.stringify(config, null, 2));
15
16
  }
16
- function findMdxFiles(dir) {
17
- const files = [];
18
- function walk(currentDir) {
19
- const entries = readdirSync(currentDir);
20
- for (const entry of entries) {
21
- const fullPath = join(currentDir, entry);
22
- const stat = statSync(fullPath);
23
- if (stat.isDirectory() && !entry.startsWith('.') && entry !== 'node_modules') {
24
- walk(fullPath);
25
- }
26
- else if (stat.isFile() && (extname(entry) === '.mdx' || extname(entry) === '.md')) {
27
- files.push(fullPath);
28
- }
29
- }
30
- }
31
- walk(dir);
32
- return files;
33
- }
34
17
  export const i18nCommand = new Command('i18n')
35
18
  .description('Manage documentation translations');
36
19
  i18nCommand
@@ -9,49 +9,56 @@ export const initCommand = new Command('init')
9
9
  .argument('[directory]', 'Target directory', '.')
10
10
  .option('--name <name>', 'Project name', 'my-docs')
11
11
  .action(async (directory, options) => {
12
- const targetDir = resolve(directory);
13
- console.log('skrypt init');
14
- console.log(` directory: ${targetDir}`);
15
- console.log(` name: ${options.name}`);
16
- console.log('');
17
- // Check if directory exists and is not empty
18
- if (existsSync(targetDir)) {
19
- const files = readdirSync(targetDir);
20
- if (files.length > 0 && !files.every((f) => f.startsWith('.'))) {
21
- console.error('Error: Target directory is not empty');
12
+ try {
13
+ const targetDir = resolve(directory);
14
+ console.log('skrypt init');
15
+ console.log(` directory: ${targetDir}`);
16
+ console.log(` name: ${options.name}`);
17
+ console.log('');
18
+ // Check if directory exists and is not empty
19
+ if (existsSync(targetDir)) {
20
+ const files = readdirSync(targetDir);
21
+ if (files.length > 0 && !files.every((f) => f.startsWith('.'))) {
22
+ console.error('Error: Target directory is not empty');
23
+ process.exit(1);
24
+ }
25
+ }
26
+ // Create target directory
27
+ mkdirSync(targetDir, { recursive: true });
28
+ // Copy template (from dist/template/, one level up from dist/commands/)
29
+ const templateDir = join(__dirname, '..', 'template');
30
+ if (!existsSync(templateDir)) {
31
+ console.error('Error: Template not found. Please reinstall skrypt.');
22
32
  process.exit(1);
23
33
  }
34
+ console.log('Creating documentation site...');
35
+ cpSync(templateDir, targetDir, { recursive: true });
36
+ // Update package.json with project name
37
+ const packageJsonPath = join(targetDir, 'package.json');
38
+ const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
39
+ packageJson.name = options.name;
40
+ writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2));
41
+ // Update docs.json with project name
42
+ const docsJsonPath = join(targetDir, 'docs.json');
43
+ const docsJson = JSON.parse(readFileSync(docsJsonPath, 'utf-8'));
44
+ docsJson.name = options.name;
45
+ docsJson.description = `${options.name} documentation`;
46
+ writeFileSync(docsJsonPath, JSON.stringify(docsJson, null, 2));
47
+ console.log('');
48
+ console.log('Done! Next steps:');
49
+ console.log('');
50
+ console.log(` cd ${directory === '.' ? '.' : directory}`);
51
+ console.log(' npm install');
52
+ console.log(' npm run dev');
53
+ console.log('');
54
+ console.log('Then open http://localhost:3000 to see your docs.');
55
+ console.log('');
56
+ console.log('To generate API documentation:');
57
+ console.log(' skrypt generate ./src -o ./content/docs');
24
58
  }
25
- // Create target directory
26
- mkdirSync(targetDir, { recursive: true });
27
- // Copy template (from dist/template/, one level up from dist/commands/)
28
- const templateDir = join(__dirname, '..', 'template');
29
- if (!existsSync(templateDir)) {
30
- console.error('Error: Template not found. Please reinstall skrypt.');
59
+ catch (err) {
60
+ const message = err instanceof Error ? err.message : String(err);
61
+ console.error(`Error: ${message}`);
31
62
  process.exit(1);
32
63
  }
33
- console.log('Creating documentation site...');
34
- cpSync(templateDir, targetDir, { recursive: true });
35
- // Update package.json with project name
36
- const packageJsonPath = join(targetDir, 'package.json');
37
- const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
38
- packageJson.name = options.name;
39
- writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2));
40
- // Update docs.json with project name
41
- const docsJsonPath = join(targetDir, 'docs.json');
42
- const docsJson = JSON.parse(readFileSync(docsJsonPath, 'utf-8'));
43
- docsJson.name = options.name;
44
- docsJson.description = `${options.name} documentation`;
45
- writeFileSync(docsJsonPath, JSON.stringify(docsJson, null, 2));
46
- console.log('');
47
- console.log('Done! Next steps:');
48
- console.log('');
49
- console.log(` cd ${directory === '.' ? '.' : directory}`);
50
- console.log(' npm install');
51
- console.log(' npm run dev');
52
- console.log('');
53
- console.log('Then open http://localhost:3000 to see your docs.');
54
- console.log('');
55
- console.log('To generate API documentation:');
56
- console.log(' skrypt generate ./src -o ./content/docs');
57
64
  });
@@ -1,6 +1,7 @@
1
1
  import { Command } from 'commander';
2
- import { existsSync, readFileSync, readdirSync, statSync } from 'fs';
3
- import { resolve, join, extname } from 'path';
2
+ import { existsSync, readFileSync, statSync } from 'fs';
3
+ import { resolve } from 'path';
4
+ import { findMdxFiles } from '../utils/files.js';
4
5
  const RULES = {
5
6
  'heading-structure': {
6
7
  description: 'Headings should follow proper hierarchy (h1 > h2 > h3)',
@@ -35,24 +36,6 @@ const RULES = {
35
36
  severity: 'warning',
36
37
  },
37
38
  };
38
- function findMdxFiles(dir) {
39
- const files = [];
40
- function walk(currentDir) {
41
- const entries = readdirSync(currentDir);
42
- for (const entry of entries) {
43
- const fullPath = join(currentDir, entry);
44
- const stat = statSync(fullPath);
45
- if (stat.isDirectory() && !entry.startsWith('.') && entry !== 'node_modules') {
46
- walk(fullPath);
47
- }
48
- else if (stat.isFile() && (extname(entry) === '.mdx' || extname(entry) === '.md')) {
49
- files.push(fullPath);
50
- }
51
- }
52
- }
53
- walk(dir);
54
- return files;
55
- }
56
39
  function lintFile(filePath) {
57
40
  const content = readFileSync(filePath, 'utf-8');
58
41
  const lines = content.split('\n');