skrypt-ai 0.8.0 → 0.8.1

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 (101) hide show
  1. package/dist/auth/index.js +6 -0
  2. package/dist/binding/binder.d.ts +5 -0
  3. package/dist/binding/binder.js +63 -0
  4. package/dist/binding/detector.d.ts +5 -0
  5. package/dist/binding/detector.js +51 -0
  6. package/dist/binding/doc-parser.d.ts +9 -0
  7. package/dist/binding/doc-parser.js +138 -0
  8. package/dist/binding/extractor.d.ts +14 -0
  9. package/dist/binding/extractor.js +39 -0
  10. package/dist/binding/index.d.ts +5 -0
  11. package/dist/binding/index.js +5 -0
  12. package/dist/binding/types.d.ts +74 -0
  13. package/dist/binding/types.js +1 -0
  14. package/dist/claims/extractor.d.ts +13 -0
  15. package/dist/claims/extractor.js +138 -0
  16. package/dist/claims/index.d.ts +4 -0
  17. package/dist/claims/index.js +4 -0
  18. package/dist/claims/reporter.d.ts +9 -0
  19. package/dist/claims/reporter.js +65 -0
  20. package/dist/claims/store.d.ts +13 -0
  21. package/dist/claims/store.js +51 -0
  22. package/dist/claims/types.d.ts +34 -0
  23. package/dist/claims/types.js +1 -0
  24. package/dist/cli.js +516 -56
  25. package/dist/commands/bind.d.ts +2 -0
  26. package/dist/commands/bind.js +139 -0
  27. package/dist/commands/claims.d.ts +2 -0
  28. package/dist/commands/claims.js +84 -0
  29. package/dist/commands/coverage.d.ts +2 -0
  30. package/dist/commands/coverage.js +61 -0
  31. package/dist/commands/generate/index.js +5 -0
  32. package/dist/commands/generate/scan.js +33 -14
  33. package/dist/commands/generate/write.d.ts +7 -0
  34. package/dist/commands/generate/write.js +65 -1
  35. package/dist/commands/import.js +12 -3
  36. package/dist/commands/init.js +68 -5
  37. package/dist/commands/monitor.d.ts +15 -0
  38. package/dist/commands/monitor.js +2 -2
  39. package/dist/commands/mutate.d.ts +2 -0
  40. package/dist/commands/mutate.js +177 -0
  41. package/dist/config/types.js +2 -2
  42. package/dist/coverage/calculator.d.ts +7 -0
  43. package/dist/coverage/calculator.js +86 -0
  44. package/dist/coverage/index.d.ts +3 -0
  45. package/dist/coverage/index.js +3 -0
  46. package/dist/coverage/reporter.d.ts +9 -0
  47. package/dist/coverage/reporter.js +65 -0
  48. package/dist/coverage/types.d.ts +36 -0
  49. package/dist/coverage/types.js +1 -0
  50. package/dist/generator/generator.d.ts +3 -1
  51. package/dist/generator/generator.js +137 -23
  52. package/dist/generator/mdx-serializer.js +3 -2
  53. package/dist/generator/organizer.d.ts +5 -1
  54. package/dist/generator/organizer.js +29 -14
  55. package/dist/generator/types.d.ts +6 -0
  56. package/dist/generator/writer.js +7 -2
  57. package/dist/github/org-discovery.js +5 -0
  58. package/dist/importers/mintlify.js +4 -3
  59. package/dist/llm/anthropic-client.js +1 -0
  60. package/dist/llm/index.d.ts +15 -0
  61. package/dist/llm/index.js +148 -29
  62. package/dist/llm/openai-client.js +2 -0
  63. package/dist/mutation/index.d.ts +4 -0
  64. package/dist/mutation/index.js +4 -0
  65. package/dist/mutation/mutator.d.ts +5 -0
  66. package/dist/mutation/mutator.js +101 -0
  67. package/dist/mutation/reporter.d.ts +14 -0
  68. package/dist/mutation/reporter.js +66 -0
  69. package/dist/mutation/runner.d.ts +9 -0
  70. package/dist/mutation/runner.js +70 -0
  71. package/dist/mutation/types.d.ts +51 -0
  72. package/dist/mutation/types.js +1 -0
  73. package/dist/qa/checks.d.ts +1 -0
  74. package/dist/qa/checks.js +47 -0
  75. package/dist/qa/index.js +2 -1
  76. package/dist/scanner/index.js +78 -11
  77. package/dist/scanner/typescript.js +42 -31
  78. package/dist/sentry.d.ts +3 -0
  79. package/dist/sentry.js +28 -0
  80. package/dist/template/docs.json +6 -3
  81. package/dist/template/next.config.mjs +15 -1
  82. package/dist/template/package.json +4 -3
  83. package/dist/template/public/docs-schema.json +257 -0
  84. package/dist/template/sentry.client.config.ts +12 -0
  85. package/dist/template/sentry.edge.config.ts +7 -0
  86. package/dist/template/sentry.server.config.ts +7 -0
  87. package/dist/template/src/app/docs/[...slug]/page.tsx +11 -5
  88. package/dist/template/src/app/docs/layout.tsx +2 -4
  89. package/dist/template/src/app/global-error.tsx +60 -0
  90. package/dist/template/src/app/layout.tsx +7 -16
  91. package/dist/template/src/app/page.tsx +2 -5
  92. package/dist/template/src/components/ai-chat-impl.tsx +1 -1
  93. package/dist/template/src/components/docs-layout.tsx +1 -15
  94. package/dist/template/src/components/footer.tsx +95 -19
  95. package/dist/template/src/components/header.tsx +1 -1
  96. package/dist/template/src/components/search-dialog.tsx +5 -0
  97. package/dist/template/src/instrumentation.ts +11 -0
  98. package/dist/template/src/lib/docs-config.ts +235 -0
  99. package/dist/template/src/lib/fonts.ts +3 -3
  100. package/dist/testing/runner.js +8 -1
  101. package/package.json +2 -1
@@ -123,7 +123,12 @@ export function SearchDialog({ open, onClose }: SearchDialogProps) {
123
123
  setSelectedIndex(0)
124
124
  setRecentSearches(getRecentSearches())
125
125
 
126
+ // Lock body scroll while dialog is open
127
+ const previousOverflow = document.body.style.overflow
128
+ document.body.style.overflow = 'hidden'
129
+
126
130
  return () => {
131
+ document.body.style.overflow = previousOverflow
127
132
  previouslyFocused?.focus()
128
133
  }
129
134
  }
@@ -0,0 +1,11 @@
1
+ export async function register() {
2
+ if (process.env.NEXT_RUNTIME === 'nodejs') {
3
+ await import('../sentry.server.config')
4
+ }
5
+
6
+ if (process.env.NEXT_RUNTIME === 'edge') {
7
+ await import('../sentry.edge.config')
8
+ }
9
+ }
10
+
11
+ export { captureRequestError as onRequestError } from '@sentry/nextjs'
@@ -0,0 +1,235 @@
1
+ /**
2
+ * Centralized docs.json config loader with validation.
3
+ *
4
+ * Instead of raw JSON.parse scattered across files, all config
5
+ * access goes through loadDocsConfig() which validates, warns
6
+ * on bad values, and falls back to safe defaults.
7
+ */
8
+
9
+ import { readFileSync } from 'fs'
10
+ import { join } from 'path'
11
+ import { SUPPORTED_SANS_FONTS, SUPPORTED_MONO_FONTS } from './fonts'
12
+ import type { Navigation } from './navigation'
13
+
14
+ // ---------------------------------------------------------------------------
15
+ // Types
16
+ // ---------------------------------------------------------------------------
17
+
18
+ export interface DocsConfig {
19
+ $schema?: string
20
+ name: string
21
+ description?: string
22
+ favicon?: string
23
+ siteUrl?: string
24
+ logo?: string
25
+ headerLinks?: Array<{ title: string; path: string }>
26
+ fonts?: {
27
+ sans?: string
28
+ mono?: string
29
+ }
30
+ theme?: {
31
+ primaryColor?: string
32
+ font?: string // legacy
33
+ }
34
+ navigation: Navigation
35
+ footer?: {
36
+ links?: Array<{ title: string; url: string }>
37
+ social?: {
38
+ github?: string
39
+ twitter?: string
40
+ discord?: string
41
+ linkedin?: string
42
+ }
43
+ copyright?: string
44
+ }
45
+ editLink?: {
46
+ repoUrl?: string
47
+ branch?: string
48
+ docsPath?: string
49
+ }
50
+ }
51
+
52
+ // ---------------------------------------------------------------------------
53
+ // Defaults
54
+ // ---------------------------------------------------------------------------
55
+
56
+ const DEFAULT_CONFIG: DocsConfig = {
57
+ name: 'Documentation',
58
+ navigation: [],
59
+ }
60
+
61
+ // ---------------------------------------------------------------------------
62
+ // Validation helpers
63
+ // ---------------------------------------------------------------------------
64
+
65
+ const HEX_COLOR_RE = /^#[0-9a-fA-F]{3}(?:[0-9a-fA-F]{3})?$/
66
+
67
+ function isNonEmptyString(value: unknown): value is string {
68
+ return typeof value === 'string' && value.trim().length > 0
69
+ }
70
+
71
+ function isHexColor(value: unknown): value is string {
72
+ return typeof value === 'string' && HEX_COLOR_RE.test(value)
73
+ }
74
+
75
+ function isArray(value: unknown): value is unknown[] {
76
+ return Array.isArray(value)
77
+ }
78
+
79
+ // ---------------------------------------------------------------------------
80
+ // Loader
81
+ // ---------------------------------------------------------------------------
82
+
83
+ /** Module-level cache so we only read + validate once per build */
84
+ let _cached: DocsConfig | null = null
85
+
86
+ /**
87
+ * Load and validate docs.json. Returns a typed DocsConfig with safe defaults.
88
+ *
89
+ * - Missing or unreadable file: returns defaults, logs warning.
90
+ * - Invalid JSON: returns defaults, logs warning.
91
+ * - Invalid field values: uses defaults for that field, logs warning.
92
+ *
93
+ * Result is cached for the lifetime of the process (one Next.js build).
94
+ */
95
+ export function loadDocsConfig(): DocsConfig {
96
+ if (_cached) return _cached
97
+
98
+ const configPath = join(process.cwd(), 'docs.json')
99
+
100
+ // --- Read file ---
101
+ let raw: string
102
+ try {
103
+ raw = readFileSync(configPath, 'utf-8')
104
+ } catch {
105
+ console.warn('[docs.json] File not found or unreadable at', configPath, '— using defaults.')
106
+ _cached = { ...DEFAULT_CONFIG }
107
+ return _cached
108
+ }
109
+
110
+ // --- Parse JSON ---
111
+ let parsed: Record<string, unknown>
112
+ try {
113
+ parsed = JSON.parse(raw)
114
+ } catch (err) {
115
+ const message = err instanceof Error ? err.message : String(err)
116
+ console.warn(`[docs.json] Invalid JSON: ${message} — using defaults.`)
117
+ _cached = { ...DEFAULT_CONFIG }
118
+ return _cached
119
+ }
120
+
121
+ if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
122
+ console.warn('[docs.json] Expected a JSON object, got', typeof parsed, '— using defaults.')
123
+ _cached = { ...DEFAULT_CONFIG }
124
+ return _cached
125
+ }
126
+
127
+ // --- Validate individual fields ---
128
+ const warnings: string[] = []
129
+
130
+ // name (required string)
131
+ let name = DEFAULT_CONFIG.name
132
+ if (isNonEmptyString(parsed.name)) {
133
+ name = parsed.name as string
134
+ } else if (parsed.name !== undefined) {
135
+ warnings.push(`"name" must be a non-empty string, got ${JSON.stringify(parsed.name)} — using "${DEFAULT_CONFIG.name}".`)
136
+ }
137
+
138
+ // description (optional string)
139
+ const description = isNonEmptyString(parsed.description) ? (parsed.description as string) : undefined
140
+
141
+ // favicon (optional string)
142
+ const favicon = isNonEmptyString(parsed.favicon) ? (parsed.favicon as string) : undefined
143
+
144
+ // siteUrl (optional string)
145
+ const siteUrl = isNonEmptyString(parsed.siteUrl) ? (parsed.siteUrl as string) : undefined
146
+
147
+ // logo (optional string)
148
+ const logo = isNonEmptyString(parsed.logo) ? (parsed.logo as string) : undefined
149
+
150
+ // headerLinks (optional array)
151
+ const headerLinks = isArray(parsed.headerLinks)
152
+ ? (parsed.headerLinks as Array<{ title: string; path: string }>)
153
+ : undefined
154
+
155
+ // navigation (required non-empty array)
156
+ let navigation: Navigation = DEFAULT_CONFIG.navigation
157
+ if (isArray(parsed.navigation) && parsed.navigation.length > 0) {
158
+ navigation = parsed.navigation as Navigation
159
+ } else if (!isArray(parsed.navigation)) {
160
+ warnings.push(`"navigation" must be an array, got ${typeof parsed.navigation} — using empty navigation.`)
161
+ } else if (parsed.navigation.length === 0) {
162
+ warnings.push('"navigation" is empty — the sidebar will have no links.')
163
+ }
164
+
165
+ // theme (optional object)
166
+ let theme: DocsConfig['theme'] = undefined
167
+ if (parsed.theme && typeof parsed.theme === 'object' && !Array.isArray(parsed.theme)) {
168
+ const rawTheme = parsed.theme as Record<string, unknown>
169
+ const primaryColor = isHexColor(rawTheme.primaryColor) ? rawTheme.primaryColor : undefined
170
+ const font = isNonEmptyString(rawTheme.font) ? (rawTheme.font as string) : undefined
171
+
172
+ if (rawTheme.primaryColor !== undefined && !primaryColor) {
173
+ warnings.push(`"theme.primaryColor" must be a valid hex color (e.g. "#171717"), got ${JSON.stringify(rawTheme.primaryColor)} — ignoring.`)
174
+ }
175
+
176
+ theme = { primaryColor, font }
177
+ }
178
+
179
+ // fonts (optional object with known font names)
180
+ let fonts: DocsConfig['fonts'] = undefined
181
+ if (parsed.fonts && typeof parsed.fonts === 'object' && !Array.isArray(parsed.fonts)) {
182
+ const rawFonts = parsed.fonts as Record<string, unknown>
183
+ let sans: string | undefined
184
+ let mono: string | undefined
185
+
186
+ if (isNonEmptyString(rawFonts.sans)) {
187
+ if (rawFonts.sans in SUPPORTED_SANS_FONTS) {
188
+ sans = rawFonts.sans as string
189
+ } else {
190
+ warnings.push(`"fonts.sans" value "${rawFonts.sans}" is not a supported font — falling back to default. Supported: ${Object.keys(SUPPORTED_SANS_FONTS).join(', ')}.`)
191
+ }
192
+ }
193
+
194
+ if (isNonEmptyString(rawFonts.mono)) {
195
+ if (rawFonts.mono in SUPPORTED_MONO_FONTS) {
196
+ mono = rawFonts.mono as string
197
+ } else {
198
+ warnings.push(`"fonts.mono" value "${rawFonts.mono}" is not a supported font — falling back to default. Supported: ${Object.keys(SUPPORTED_MONO_FONTS).join(', ')}.`)
199
+ }
200
+ }
201
+
202
+ fonts = { sans, mono }
203
+ }
204
+
205
+ // footer (optional)
206
+ const footer = parsed.footer && typeof parsed.footer === 'object' && !Array.isArray(parsed.footer)
207
+ ? (parsed.footer as DocsConfig['footer'])
208
+ : undefined
209
+
210
+ // editLink (optional)
211
+ const editLink = parsed.editLink && typeof parsed.editLink === 'object' && !Array.isArray(parsed.editLink)
212
+ ? (parsed.editLink as DocsConfig['editLink'])
213
+ : undefined
214
+
215
+ // --- Log warnings ---
216
+ for (const w of warnings) {
217
+ console.warn(`[docs.json] ${w}`)
218
+ }
219
+
220
+ _cached = {
221
+ name,
222
+ description,
223
+ favicon,
224
+ siteUrl,
225
+ logo,
226
+ headerLinks,
227
+ fonts,
228
+ theme,
229
+ navigation,
230
+ footer,
231
+ editLink,
232
+ }
233
+
234
+ return _cached
235
+ }
@@ -83,9 +83,9 @@ function sanitize(name: string): string {
83
83
  *
84
84
  * Unrecognized font names fall back to defaults.
85
85
  */
86
- export function resolveFonts(docsConfig: Record<string, unknown>): ResolvedFonts {
87
- const fonts = docsConfig.fonts as { sans?: string; mono?: string } | undefined
88
- const legacyFont = (docsConfig.theme as { font?: string } | undefined)?.font
86
+ export function resolveFonts(docsConfig: { fonts?: { sans?: string; mono?: string }; theme?: { font?: string } }): ResolvedFonts {
87
+ const fonts = docsConfig.fonts
88
+ const legacyFont = docsConfig.theme?.font
89
89
 
90
90
  // Resolve sans font
91
91
  const rawSans = fonts?.sans || legacyFont || DEFAULT_SANS
@@ -159,7 +159,7 @@ async function installNodeDeps(deps, tempDir, envVars) {
159
159
  }
160
160
  writeFileSync(join(tempDir, 'package.json'), JSON.stringify(pkg));
161
161
  const env = buildCleanEnv(envVars);
162
- await executeWithTimeout('npm', ['install', '--prefix', tempDir, '--no-audit', '--no-fund'], 30000, tempDir, env);
162
+ await executeWithTimeout('npm', ['install', '--prefix', tempDir, '--no-audit', '--no-fund', '--ignore-scripts'], 30000, tempDir, env);
163
163
  }
164
164
  /**
165
165
  * Install Python dependencies in a temp directory using pip
@@ -184,6 +184,7 @@ function executeWithTimeout(command, args, timeoutMs, cwd, env) {
184
184
  let stdout = '';
185
185
  let stderr = '';
186
186
  let timedOut = false;
187
+ let resolved = false;
187
188
  const MAX_BUFFER = 1024 * 1024; // 1MB cap to prevent OOM
188
189
  const timeout = setTimeout(() => {
189
190
  timedOut = true;
@@ -204,6 +205,9 @@ function executeWithTimeout(command, args, timeoutMs, cwd, env) {
204
205
  }
205
206
  });
206
207
  proc.on('close', (code) => {
208
+ if (resolved)
209
+ return;
210
+ resolved = true;
207
211
  clearTimeout(timeout);
208
212
  resolve({
209
213
  exitCode: timedOut ? 1 : (code ?? 1),
@@ -213,6 +217,9 @@ function executeWithTimeout(command, args, timeoutMs, cwd, env) {
213
217
  });
214
218
  });
215
219
  proc.on('error', (err) => {
220
+ if (resolved)
221
+ return;
222
+ resolved = true;
216
223
  clearTimeout(timeout);
217
224
  resolve({
218
225
  exitCode: 1,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skrypt-ai",
3
- "version": "0.8.0",
3
+ "version": "0.8.1",
4
4
  "description": "AI-powered documentation generator with code examples",
5
5
  "type": "module",
6
6
  "main": "dist/cli.js",
@@ -48,6 +48,7 @@
48
48
  "license": "Elastic-2.0",
49
49
  "dependencies": {
50
50
  "@anthropic-ai/sdk": "^0.78.0",
51
+ "@sentry/node": "^10.45.0",
51
52
  "chokidar": "^5.0.0",
52
53
  "commander": "^14.0.3",
53
54
  "js-yaml": "^4.1.1",