@hesed/webui 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (122) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +67 -0
  3. package/bin/dev.cmd +3 -0
  4. package/bin/dev.js +5 -0
  5. package/bin/run.cmd +3 -0
  6. package/bin/run.js +5 -0
  7. package/dist/commands/webui.d.ts +12 -0
  8. package/dist/commands/webui.js +52 -0
  9. package/dist/index.d.ts +1 -0
  10. package/dist/index.js +1 -0
  11. package/dist/lib/executor.d.ts +9 -0
  12. package/dist/lib/executor.js +42 -0
  13. package/dist/lib/introspect.d.ts +41 -0
  14. package/dist/lib/introspect.js +44 -0
  15. package/dist/lib/server.d.ts +17 -0
  16. package/dist/lib/server.js +98 -0
  17. package/oclif.manifest.json +54 -0
  18. package/package.json +88 -0
  19. package/web/.next/BUILD_ID +1 -0
  20. package/web/.next/app-build-manifest.json +26 -0
  21. package/web/.next/app-path-routes-manifest.json +4 -0
  22. package/web/.next/build-manifest.json +33 -0
  23. package/web/.next/cache/.previewinfo +1 -0
  24. package/web/.next/cache/.rscinfo +1 -0
  25. package/web/.next/cache/.tsbuildinfo +1 -0
  26. package/web/.next/cache/eslint/.cache_1ki824o +1 -0
  27. package/web/.next/cache/next-devtools-config.json +1 -0
  28. package/web/.next/cache/webpack/client-development/0.pack.gz +0 -0
  29. package/web/.next/cache/webpack/client-development/1.pack.gz +0 -0
  30. package/web/.next/cache/webpack/client-development/index.pack.gz +0 -0
  31. package/web/.next/cache/webpack/client-development/index.pack.gz.old +0 -0
  32. package/web/.next/cache/webpack/client-production/0.pack +0 -0
  33. package/web/.next/cache/webpack/client-production/1.pack +0 -0
  34. package/web/.next/cache/webpack/client-production/2.pack +0 -0
  35. package/web/.next/cache/webpack/client-production/3.pack +0 -0
  36. package/web/.next/cache/webpack/client-production/index.pack +0 -0
  37. package/web/.next/cache/webpack/client-production/index.pack.old +0 -0
  38. package/web/.next/cache/webpack/edge-server-production/0.pack +0 -0
  39. package/web/.next/cache/webpack/edge-server-production/index.pack +0 -0
  40. package/web/.next/cache/webpack/server-development/0.pack.gz +0 -0
  41. package/web/.next/cache/webpack/server-development/index.pack.gz +0 -0
  42. package/web/.next/cache/webpack/server-production/0.pack +0 -0
  43. package/web/.next/cache/webpack/server-production/1.pack +0 -0
  44. package/web/.next/cache/webpack/server-production/index.pack +0 -0
  45. package/web/.next/cache/webpack/server-production/index.pack.old +0 -0
  46. package/web/.next/diagnostics/build-diagnostics.json +6 -0
  47. package/web/.next/diagnostics/framework.json +1 -0
  48. package/web/.next/export-marker.json +6 -0
  49. package/web/.next/images-manifest.json +58 -0
  50. package/web/.next/next-minimal-server.js.nft.json +1 -0
  51. package/web/.next/next-server.js.nft.json +1 -0
  52. package/web/.next/package.json +1 -0
  53. package/web/.next/prerender-manifest.json +61 -0
  54. package/web/.next/react-loadable-manifest.json +1 -0
  55. package/web/.next/required-server-files.json +320 -0
  56. package/web/.next/routes-manifest.json +53 -0
  57. package/web/.next/server/app/_not-found/page.js +2 -0
  58. package/web/.next/server/app/_not-found/page.js.nft.json +1 -0
  59. package/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -0
  60. package/web/.next/server/app/_not-found.html +1 -0
  61. package/web/.next/server/app/_not-found.meta +8 -0
  62. package/web/.next/server/app/_not-found.rsc +16 -0
  63. package/web/.next/server/app/index.html +1 -0
  64. package/web/.next/server/app/index.meta +7 -0
  65. package/web/.next/server/app/index.rsc +20 -0
  66. package/web/.next/server/app/page.js +2 -0
  67. package/web/.next/server/app/page.js.nft.json +1 -0
  68. package/web/.next/server/app/page_client-reference-manifest.js +1 -0
  69. package/web/.next/server/app-paths-manifest.json +4 -0
  70. package/web/.next/server/chunks/157.js +6 -0
  71. package/web/.next/server/chunks/799.js +30 -0
  72. package/web/.next/server/functions-config-manifest.json +4 -0
  73. package/web/.next/server/interception-route-rewrite-manifest.js +1 -0
  74. package/web/.next/server/middleware-build-manifest.js +1 -0
  75. package/web/.next/server/middleware-manifest.json +6 -0
  76. package/web/.next/server/middleware-react-loadable-manifest.js +1 -0
  77. package/web/.next/server/next-font-manifest.js +1 -0
  78. package/web/.next/server/next-font-manifest.json +1 -0
  79. package/web/.next/server/pages/404.html +1 -0
  80. package/web/.next/server/pages/500.html +1 -0
  81. package/web/.next/server/pages/_app.js +1 -0
  82. package/web/.next/server/pages/_app.js.nft.json +1 -0
  83. package/web/.next/server/pages/_document.js +1 -0
  84. package/web/.next/server/pages/_document.js.nft.json +1 -0
  85. package/web/.next/server/pages/_error.js +19 -0
  86. package/web/.next/server/pages/_error.js.nft.json +1 -0
  87. package/web/.next/server/pages-manifest.json +6 -0
  88. package/web/.next/server/server-reference-manifest.js +1 -0
  89. package/web/.next/server/server-reference-manifest.json +1 -0
  90. package/web/.next/server/webpack-runtime.js +1 -0
  91. package/web/.next/static/LEwu5WvMMaoVEbVj3-KGq/_buildManifest.js +1 -0
  92. package/web/.next/static/LEwu5WvMMaoVEbVj3-KGq/_ssgManifest.js +1 -0
  93. package/web/.next/static/chunks/131-a68a87dd22cef82b.js +1 -0
  94. package/web/.next/static/chunks/app/_not-found/page-784ddbf5f1a9343a.js +1 -0
  95. package/web/.next/static/chunks/app/layout-4e0fe443358078fc.js +1 -0
  96. package/web/.next/static/chunks/app/page-d098bd39f1fcec43.js +1 -0
  97. package/web/.next/static/chunks/c7879cf7-b5ab1053c1d9a2e7.js +1 -0
  98. package/web/.next/static/chunks/framework-1934959d81242241.js +1 -0
  99. package/web/.next/static/chunks/main-a8814cf406d931cd.js +1 -0
  100. package/web/.next/static/chunks/main-app-a3b742ef05fa17a3.js +1 -0
  101. package/web/.next/static/chunks/pages/_app-930c2e1cfe9b86bc.js +1 -0
  102. package/web/.next/static/chunks/pages/_error-2a950c2742f550d2.js +1 -0
  103. package/web/.next/static/chunks/polyfills-42372ed130431b0a.js +1 -0
  104. package/web/.next/static/chunks/webpack-3046868c425865e4.js +1 -0
  105. package/web/.next/static/css/48007c73fe98a161.css +1 -0
  106. package/web/.next/trace +2 -0
  107. package/web/.next/types/app/layout.ts +84 -0
  108. package/web/.next/types/app/page.ts +84 -0
  109. package/web/.next/types/cache-life.d.ts +141 -0
  110. package/web/.next/types/package.json +1 -0
  111. package/web/.next/types/routes.d.ts +57 -0
  112. package/web/.next/types/validator.ts +61 -0
  113. package/web/app/globals.css +339 -0
  114. package/web/app/layout.tsx +20 -0
  115. package/web/app/page.tsx +57 -0
  116. package/web/components/command-detail.tsx +160 -0
  117. package/web/components/command-list.tsx +67 -0
  118. package/web/components/theme-provider.tsx +12 -0
  119. package/web/components/theme-toggle.tsx +62 -0
  120. package/web/lib/types.ts +77 -0
  121. package/web/next.config.mjs +14 -0
  122. package/web/tsconfig.json +23 -0
@@ -0,0 +1,57 @@
1
+ 'use client'
2
+
3
+ import {useEffect, useState} from 'react'
4
+
5
+ import type {CommandsResponse} from '../lib/types'
6
+
7
+ import {CommandDetail} from '../components/command-detail'
8
+ import {CommandList} from '../components/command-list'
9
+
10
+ export default function Page() {
11
+ const [data, setData] = useState<CommandsResponse | null>(null)
12
+ const [error, setError] = useState<null | string>(null)
13
+ const [query, setQuery] = useState('')
14
+ const [selectedId, setSelectedId] = useState<string>()
15
+
16
+ useEffect(() => {
17
+ fetch('/api/commands')
18
+ .then((res) => res.json())
19
+ .then((json: CommandsResponse) => setData(json))
20
+ .catch((error_) => setError(String(error_)))
21
+ }, [])
22
+
23
+ if (error) {
24
+ return <div className="empty">Failed to load commands: {error}</div>
25
+ }
26
+
27
+ if (!data) {
28
+ return <div className="empty">Loading commands…</div>
29
+ }
30
+
31
+ const selected = data.commands.find((c) => c.id === selectedId)
32
+
33
+ return (
34
+ <div className="app">
35
+ <CommandList
36
+ bin={data.bin}
37
+ commands={data.commands}
38
+ onSelect={setSelectedId}
39
+ query={query}
40
+ selectedId={selectedId}
41
+ setQuery={setQuery}
42
+ version={data.version}
43
+ />
44
+ <main className="main">
45
+ {selected ? (
46
+ <CommandDetail bin={data.bin} command={selected} />
47
+ ) : (
48
+ <div className="empty">
49
+ Select a command from the left to view its options and run it.
50
+ <br />
51
+ {data.commands.length} commands available.
52
+ </div>
53
+ )}
54
+ </main>
55
+ </div>
56
+ )
57
+ }
@@ -0,0 +1,160 @@
1
+ 'use client'
2
+
3
+ import {useMemo, useState} from 'react'
4
+
5
+ import {buildArgv, type CommandMeta, type RunResult} from '../lib/types'
6
+
7
+ export function CommandDetail({bin, command}: {bin: string; command: CommandMeta}) {
8
+ const [argValues, setArgValues] = useState<Record<string, string>>({})
9
+ const [flagValues, setFlagValues] = useState<Record<string, boolean | string>>({})
10
+ const [result, setResult] = useState<null | RunResult>(null)
11
+ const [running, setRunning] = useState(false)
12
+
13
+ // Reset form state whenever the selected command changes.
14
+ const argv = useMemo(() => buildArgv(command, argValues, flagValues), [command, argValues, flagValues])
15
+ const preview = `${bin || 'sdkck'} ${command.id}${argv.length > 0 ? ' ' + argv.join(' ') : ''}`
16
+
17
+ async function run() {
18
+ setRunning(true)
19
+ setResult(null)
20
+ try {
21
+ const res = await fetch('/api/run', {
22
+ body: JSON.stringify({argv, id: command.id}),
23
+ headers: {'content-type': 'application/json'},
24
+ method: 'POST',
25
+ })
26
+ setResult((await res.json()) as RunResult)
27
+ } catch (error) {
28
+ setResult({durationMs: 0, error: String(error), output: String(error), success: false})
29
+ } finally {
30
+ setRunning(false)
31
+ }
32
+ }
33
+
34
+ return (
35
+ <div className="detail" key={command.id}>
36
+ <h1>
37
+ {command.id}
38
+ {command.pluginName && <span className="badge">{command.pluginName}</span>}
39
+ </h1>
40
+ {(command.description || command.summary) && <p className="desc">{command.description ?? command.summary}</p>}
41
+
42
+ {command.args.length > 0 && (
43
+ <>
44
+ <div className="section-title">Arguments</div>
45
+ {command.args.map((arg) => (
46
+ <div className="field" key={arg.name}>
47
+ <label htmlFor={`arg-${arg.name}`}>
48
+ {arg.name}
49
+ {arg.required && <span className="req">*</span>}
50
+ </label>
51
+ {arg.description && <div className="hint">{arg.description}</div>}
52
+ {arg.options ? (
53
+ <select
54
+ id={`arg-${arg.name}`}
55
+ onChange={(e) => setArgValues((v) => ({...v, [arg.name]: e.target.value}))}
56
+ value={argValues[arg.name] ?? ''}
57
+ >
58
+ <option value="">— choose —</option>
59
+ {arg.options.map((opt) => (
60
+ <option key={opt} value={opt}>
61
+ {opt}
62
+ </option>
63
+ ))}
64
+ </select>
65
+ ) : (
66
+ <input
67
+ id={`arg-${arg.name}`}
68
+ onChange={(e) => setArgValues((v) => ({...v, [arg.name]: e.target.value}))}
69
+ placeholder={arg.default ? String(arg.default) : ''}
70
+ type="text"
71
+ value={argValues[arg.name] ?? ''}
72
+ />
73
+ )}
74
+ </div>
75
+ ))}
76
+ </>
77
+ )}
78
+
79
+ {command.flags.length > 0 && (
80
+ <>
81
+ <div className="section-title">Flags</div>
82
+ {command.flags.map((flag) =>
83
+ flag.type === 'boolean' ? (
84
+ <div className="field checkbox-row" key={flag.name}>
85
+ <input
86
+ checked={flagValues[flag.name] === true}
87
+ id={`flag-${flag.name}`}
88
+ onChange={(e) => setFlagValues((v) => ({...v, [flag.name]: e.target.checked}))}
89
+ type="checkbox"
90
+ />
91
+ <label htmlFor={`flag-${flag.name}`}>
92
+ --{flag.name}
93
+ {flag.description ? (
94
+ <span className="hint" style={{display: 'inline', marginLeft: 8}}>
95
+ {flag.description}
96
+ </span>
97
+ ) : null}
98
+ </label>
99
+ </div>
100
+ ) : (
101
+ <div className="field" key={flag.name}>
102
+ <label htmlFor={`flag-${flag.name}`}>
103
+ --{flag.name}
104
+ {flag.char ? ` (-${flag.char})` : ''}
105
+ {flag.required && <span className="req">*</span>}
106
+ {flag.multiple && <span className="badge">multiple</span>}
107
+ </label>
108
+ {flag.description && <div className="hint">{flag.description}</div>}
109
+ {flag.options ? (
110
+ <select
111
+ id={`flag-${flag.name}`}
112
+ onChange={(e) => setFlagValues((v) => ({...v, [flag.name]: e.target.value}))}
113
+ value={(flagValues[flag.name] as string) ?? ''}
114
+ >
115
+ <option value="">— choose —</option>
116
+ {flag.options.map((opt) => (
117
+ <option key={opt} value={opt}>
118
+ {opt}
119
+ </option>
120
+ ))}
121
+ </select>
122
+ ) : (
123
+ <input
124
+ id={`flag-${flag.name}`}
125
+ onChange={(e) => setFlagValues((v) => ({...v, [flag.name]: e.target.value}))}
126
+ placeholder={
127
+ flag.default === undefined
128
+ ? flag.multiple
129
+ ? 'comma,separated,values'
130
+ : ''
131
+ : String(flag.default)
132
+ }
133
+ type="text"
134
+ value={(flagValues[flag.name] as string) ?? ''}
135
+ />
136
+ )}
137
+ </div>
138
+ ),
139
+ )}
140
+ </>
141
+ )}
142
+
143
+ <div className="section-title">Command</div>
144
+ <div className="preview">$ {preview}</div>
145
+
146
+ <button className="run-btn" disabled={running} onClick={run} type="button">
147
+ {running ? 'Running…' : 'Run command'}
148
+ </button>
149
+
150
+ {result && (
151
+ <div className="output">
152
+ <div className={`status ${result.success ? 'ok' : 'err'}`}>
153
+ {result.success ? '✓ Success' : '✗ Failed'} · {result.durationMs}ms
154
+ </div>
155
+ <pre>{result.output || '(no output)'}</pre>
156
+ </div>
157
+ )}
158
+ </div>
159
+ )
160
+ }
@@ -0,0 +1,67 @@
1
+ 'use client'
2
+
3
+ import type {CommandMeta} from '../lib/types'
4
+
5
+ import {ThemeToggle} from './theme-toggle'
6
+
7
+ export function CommandList({
8
+ bin,
9
+ commands,
10
+ onSelect,
11
+ query,
12
+ selectedId,
13
+ setQuery,
14
+ version,
15
+ }: {
16
+ bin: string
17
+ commands: CommandMeta[]
18
+ onSelect: (id: string) => void
19
+ query: string
20
+ selectedId?: string
21
+ setQuery: (q: string) => void
22
+ version: string
23
+ }) {
24
+ const filtered = commands.filter((cmd) => {
25
+ if (!query) return true
26
+ const haystack = `${cmd.id} ${cmd.summary ?? ''} ${cmd.description ?? ''}`.toLowerCase()
27
+ return query
28
+ .toLowerCase()
29
+ .split(/\s+/)
30
+ .every((term) => haystack.includes(term))
31
+ })
32
+
33
+ return (
34
+ <aside className="sidebar">
35
+ <div className="sidebar-header">
36
+ <div className="brand-row">
37
+ <div className="brand">
38
+ {bin || 'sdkck'} <small>web UI · v{version}</small>
39
+ </div>
40
+ <ThemeToggle />
41
+ </div>
42
+ <input
43
+ aria-label="Filter commands"
44
+ className="search"
45
+ onChange={(e) => setQuery(e.target.value)}
46
+ placeholder="Filter commands…"
47
+ type="text"
48
+ value={query}
49
+ />
50
+ </div>
51
+ <nav className="command-list">
52
+ {filtered.length === 0 && <div className="empty">No commands match.</div>}
53
+ {filtered.map((cmd) => (
54
+ <button
55
+ className={`command-item${cmd.id === selectedId ? ' active' : ''}`}
56
+ key={cmd.id}
57
+ onClick={() => onSelect(cmd.id)}
58
+ type="button"
59
+ >
60
+ <span className="cmd-id">{cmd.id}</span>
61
+ <span className="cmd-summary">{cmd.summary ?? cmd.description ?? ''}</span>
62
+ </button>
63
+ ))}
64
+ </nav>
65
+ </aside>
66
+ )
67
+ }
@@ -0,0 +1,12 @@
1
+ 'use client'
2
+
3
+ import {ThemeProvider as NextThemesProvider} from 'next-themes'
4
+ import {type ReactNode} from 'react'
5
+
6
+ export function ThemeProvider({children}: {children: ReactNode}) {
7
+ return (
8
+ <NextThemesProvider attribute="class" defaultTheme="dark" disableTransitionOnChange enableSystem>
9
+ {children}
10
+ </NextThemesProvider>
11
+ )
12
+ }
@@ -0,0 +1,62 @@
1
+ 'use client'
2
+
3
+ import {useTheme} from 'next-themes'
4
+ import {useEffect, useState} from 'react'
5
+
6
+ export function ThemeToggle() {
7
+ const {setTheme, theme} = useTheme()
8
+ const [mounted, setMounted] = useState(false)
9
+
10
+ useEffect(() => {
11
+ setMounted(true)
12
+ }, [])
13
+
14
+ if (!mounted) {
15
+ return <div className="theme-toggle-placeholder" />
16
+ }
17
+
18
+ return (
19
+ <button
20
+ aria-label="Toggle theme"
21
+ className="theme-toggle"
22
+ onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')}
23
+ type="button"
24
+ >
25
+ {theme === 'dark' ? (
26
+ <svg
27
+ fill="none"
28
+ height="15"
29
+ stroke="currentColor"
30
+ strokeLinecap="round"
31
+ strokeLinejoin="round"
32
+ strokeWidth="2"
33
+ viewBox="0 0 24 24"
34
+ width="15"
35
+ >
36
+ <circle cx="12" cy="12" r="4" />
37
+ <path d="M12 2v2" />
38
+ <path d="M12 20v2" />
39
+ <path d="m4.93 4.93 1.41 1.41" />
40
+ <path d="m17.66 17.66 1.41 1.41" />
41
+ <path d="M2 12h2" />
42
+ <path d="M20 12h2" />
43
+ <path d="m6.34 17.66-1.41 1.41" />
44
+ <path d="m19.07 4.93-1.41 1.41" />
45
+ </svg>
46
+ ) : (
47
+ <svg
48
+ fill="none"
49
+ height="15"
50
+ stroke="currentColor"
51
+ strokeLinecap="round"
52
+ strokeLinejoin="round"
53
+ strokeWidth="2"
54
+ viewBox="0 0 24 24"
55
+ width="15"
56
+ >
57
+ <path d="M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z" />
58
+ </svg>
59
+ )}
60
+ </button>
61
+ )
62
+ }
@@ -0,0 +1,77 @@
1
+ export interface FlagMeta {
2
+ char?: string
3
+ default?: unknown
4
+ description?: string
5
+ multiple: boolean
6
+ name: string
7
+ options?: string[]
8
+ required: boolean
9
+ type: 'boolean' | 'option'
10
+ }
11
+
12
+ export interface ArgMeta {
13
+ default?: unknown
14
+ description?: string
15
+ name: string
16
+ options?: string[]
17
+ required: boolean
18
+ }
19
+
20
+ export interface CommandMeta {
21
+ aliases: string[]
22
+ args: ArgMeta[]
23
+ description?: string
24
+ flags: FlagMeta[]
25
+ id: string
26
+ pluginName?: string
27
+ pluginType?: string
28
+ summary?: string
29
+ usage?: string | string[]
30
+ }
31
+
32
+ export interface CommandsResponse {
33
+ bin: string
34
+ commands: CommandMeta[]
35
+ version: string
36
+ }
37
+
38
+ export interface RunResult {
39
+ durationMs: number
40
+ error?: string
41
+ output: string
42
+ success: boolean
43
+ }
44
+
45
+ /** Build an oclif-style argv array from form values for a command. */
46
+ export function buildArgv(
47
+ command: CommandMeta,
48
+ argValues: Record<string, string>,
49
+ flagValues: Record<string, boolean | string>,
50
+ ): string[] {
51
+ const argv: string[] = []
52
+
53
+ for (const arg of command.args) {
54
+ const value = argValues[arg.name]
55
+ if (value !== undefined && value !== '') argv.push(value)
56
+ }
57
+
58
+ for (const flag of command.flags) {
59
+ const value = flagValues[flag.name]
60
+ if (flag.type === 'boolean') {
61
+ if (value === true) argv.push(`--${flag.name}`)
62
+ } else if (typeof value === 'string' && value !== '') {
63
+ if (flag.multiple) {
64
+ for (const part of value
65
+ .split(',')
66
+ .map((s) => s.trim())
67
+ .filter(Boolean)) {
68
+ argv.push(`--${flag.name}`, part)
69
+ }
70
+ } else {
71
+ argv.push(`--${flag.name}`, value)
72
+ }
73
+ }
74
+ }
75
+
76
+ return argv
77
+ }
@@ -0,0 +1,14 @@
1
+ import {dirname} from 'node:path'
2
+ import {fileURLToPath} from 'node:url'
3
+
4
+ /** @type {import('next').NextConfig} */
5
+ const nextConfig = {
6
+ // The plugin ships its own lockfile alongside the host repo's; pin the trace
7
+ // root to this directory so Next doesn't guess the wrong workspace root.
8
+ outputFileTracingRoot: dirname(fileURLToPath(import.meta.url)),
9
+ // The app is always served behind the plugin's custom Node server, which owns
10
+ // the /api/* routes; Next only renders the UI.
11
+ reactStrictMode: true,
12
+ }
13
+
14
+ export default nextConfig
@@ -0,0 +1,23 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "lib": ["dom", "dom.iterable", "esnext"],
5
+ "allowJs": true,
6
+ "skipLibCheck": true,
7
+ "strict": true,
8
+ "noEmit": true,
9
+ "esModuleInterop": true,
10
+ "module": "esnext",
11
+ "moduleResolution": "bundler",
12
+ "resolveJsonModule": true,
13
+ "isolatedModules": true,
14
+ "jsx": "preserve",
15
+ "incremental": true,
16
+ "plugins": [{"name": "next"}],
17
+ "paths": {
18
+ "@/*": ["./*"]
19
+ }
20
+ },
21
+ "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
22
+ "exclude": ["node_modules"]
23
+ }