@jkwd/inbase 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.
- package/README.md +76 -0
- package/apps/explorer/index.html +12 -0
- package/apps/explorer/package.json +28 -0
- package/apps/explorer/scripts/js-source.mjs +188 -0
- package/apps/explorer/scripts/patch-lib.d.ts +115 -0
- package/apps/explorer/scripts/patch-lib.mjs +472 -0
- package/apps/explorer/scripts/scan-target.mjs +188 -0
- package/apps/explorer/scripts/session-store.d.ts +156 -0
- package/apps/explorer/scripts/session-store.mjs +809 -0
- package/apps/explorer/scripts/target-config.d.ts +8 -0
- package/apps/explorer/scripts/target-config.mjs +42 -0
- package/apps/explorer/src/App.tsx +941 -0
- package/apps/explorer/src/agentIntent.ts +182 -0
- package/apps/explorer/src/codebase.ts +15 -0
- package/apps/explorer/src/index.css +632 -0
- package/apps/explorer/src/layout.ts +508 -0
- package/apps/explorer/src/main.tsx +16 -0
- package/apps/explorer/src/scene/BlockPlacer.tsx +87 -0
- package/apps/explorer/src/scene/Bridge.tsx +290 -0
- package/apps/explorer/src/scene/FileBlock.tsx +256 -0
- package/apps/explorer/src/scene/FolderArea.tsx +96 -0
- package/apps/explorer/src/scene/IslandPlacer.tsx +40 -0
- package/apps/explorer/src/scene/MapSelectBorder.tsx +57 -0
- package/apps/explorer/src/scene/MapView.tsx +247 -0
- package/apps/explorer/src/scene/Player.tsx +245 -0
- package/apps/explorer/src/scene/RelationLines.tsx +223 -0
- package/apps/explorer/src/scene/SelectionController.tsx +89 -0
- package/apps/explorer/src/scene/UserContextTracker.tsx +152 -0
- package/apps/explorer/src/scene/World.tsx +323 -0
- package/apps/explorer/src/theme.ts +111 -0
- package/apps/explorer/src/types.ts +245 -0
- package/apps/explorer/src/ui/CanvasErrorBoundary.tsx +38 -0
- package/apps/explorer/src/ui/HUD.tsx +1090 -0
- package/apps/explorer/src/ui/NameInput.tsx +45 -0
- package/apps/explorer/src/userContext.ts +73 -0
- package/apps/explorer/src/userCreated.ts +354 -0
- package/apps/explorer/src/vite-env.d.ts +1 -0
- package/apps/explorer/tsconfig.json +21 -0
- package/apps/explorer/vite.config.ts +295 -0
- package/bin/inbase.mjs +170 -0
- package/bin/project.mjs +94 -0
- package/bin/session.mjs +241 -0
- package/package.json +63 -0
- package/skill/inbase/SKILL.md +167 -0
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { useEffect, useRef } from 'react'
|
|
2
|
+
|
|
3
|
+
type NameInputProps = {
|
|
4
|
+
placeholder: string
|
|
5
|
+
onCommit: (name: string) => void
|
|
6
|
+
onCancel: () => void
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function NameInput({ placeholder, onCommit, onCancel }: NameInputProps) {
|
|
10
|
+
const input = useRef<HTMLInputElement>(null)
|
|
11
|
+
|
|
12
|
+
useEffect(() => {
|
|
13
|
+
const timer = window.setTimeout(() => input.current?.focus(), 40)
|
|
14
|
+
return () => window.clearTimeout(timer)
|
|
15
|
+
}, [])
|
|
16
|
+
|
|
17
|
+
return (
|
|
18
|
+
<form
|
|
19
|
+
className="block-name-form"
|
|
20
|
+
onPointerDown={(event) => event.stopPropagation()}
|
|
21
|
+
onSubmit={(event) => {
|
|
22
|
+
event.preventDefault()
|
|
23
|
+
const value = input.current?.value ?? ''
|
|
24
|
+
if (!value.trim()) return
|
|
25
|
+
onCommit(value)
|
|
26
|
+
}}
|
|
27
|
+
>
|
|
28
|
+
<input
|
|
29
|
+
ref={input}
|
|
30
|
+
className="block-name-input"
|
|
31
|
+
placeholder={placeholder}
|
|
32
|
+
aria-label={placeholder}
|
|
33
|
+
autoComplete="off"
|
|
34
|
+
spellCheck={false}
|
|
35
|
+
onKeyDown={(event) => {
|
|
36
|
+
event.stopPropagation()
|
|
37
|
+
if (event.code === 'Escape') {
|
|
38
|
+
event.preventDefault()
|
|
39
|
+
onCancel()
|
|
40
|
+
}
|
|
41
|
+
}}
|
|
42
|
+
/>
|
|
43
|
+
</form>
|
|
44
|
+
)
|
|
45
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
CodebaseGraph,
|
|
3
|
+
FileNode,
|
|
4
|
+
UserContext,
|
|
5
|
+
UserFileRef,
|
|
6
|
+
} from './types'
|
|
7
|
+
|
|
8
|
+
export function toFileRef(file: FileNode): UserFileRef {
|
|
9
|
+
return {
|
|
10
|
+
id: file.id,
|
|
11
|
+
name: file.name,
|
|
12
|
+
path: file.path,
|
|
13
|
+
folder: file.folder,
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
let timer: number | null = null
|
|
18
|
+
let pending: UserContext | null = null
|
|
19
|
+
let lastWritten = ''
|
|
20
|
+
|
|
21
|
+
export async function fetchUserContext(): Promise<UserContext | null> {
|
|
22
|
+
try {
|
|
23
|
+
const response = await fetch('/api/user-context')
|
|
24
|
+
if (!response.ok) return null
|
|
25
|
+
return (await response.json()) as UserContext
|
|
26
|
+
} catch {
|
|
27
|
+
return null
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function persistFollowLook(followLook: boolean) {
|
|
32
|
+
fetch('/api/user-context', {
|
|
33
|
+
method: 'POST',
|
|
34
|
+
headers: { 'Content-Type': 'application/json' },
|
|
35
|
+
body: `${JSON.stringify({ followLook })}\n`,
|
|
36
|
+
}).catch(() => {
|
|
37
|
+
lastWritten = ''
|
|
38
|
+
})
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function persistUserContext(context: UserContext) {
|
|
42
|
+
pending = context
|
|
43
|
+
if (timer !== null) return
|
|
44
|
+
timer = window.setTimeout(flushUserContext, 400)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function flushUserContext() {
|
|
48
|
+
timer = null
|
|
49
|
+
const context = pending
|
|
50
|
+
pending = null
|
|
51
|
+
if (!context) return
|
|
52
|
+
const {
|
|
53
|
+
followLook: _followLook,
|
|
54
|
+
userCreatedBlocks: _userCreatedBlocks,
|
|
55
|
+
userCreatedIslands: _userCreatedIslands,
|
|
56
|
+
...gaze
|
|
57
|
+
} = context
|
|
58
|
+
const body = JSON.stringify(gaze, null, 2)
|
|
59
|
+
if (body === lastWritten) return
|
|
60
|
+
lastWritten = body
|
|
61
|
+
fetch('/api/user-context', {
|
|
62
|
+
method: 'POST',
|
|
63
|
+
headers: { 'Content-Type': 'application/json' },
|
|
64
|
+
body: `${body}\n`,
|
|
65
|
+
}).catch(() => {
|
|
66
|
+
lastWritten = ''
|
|
67
|
+
})
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function fileById(graph: CodebaseGraph, id: string | null) {
|
|
71
|
+
if (!id) return null
|
|
72
|
+
return graph.files.find((file) => file.id === id) ?? null
|
|
73
|
+
}
|
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
import { CONFIG, fileHeight } from './theme'
|
|
2
|
+
import { folderOfFile, folderParent } from './layout'
|
|
3
|
+
import type {
|
|
4
|
+
CodebaseGraph,
|
|
5
|
+
FileNode,
|
|
6
|
+
PatchImportAddition,
|
|
7
|
+
PatchSymbolAddition,
|
|
8
|
+
UserCreatedBlock,
|
|
9
|
+
UserCreatedIsland,
|
|
10
|
+
WorldLayout,
|
|
11
|
+
} from './types'
|
|
12
|
+
|
|
13
|
+
export function languageOfName(name: string) {
|
|
14
|
+
const ext = name.split('.').pop()?.toLowerCase()
|
|
15
|
+
return ext && ext !== name ? ext : 'txt'
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function toCreatedFile(block: UserCreatedBlock): FileNode {
|
|
19
|
+
const name = block.naming && !block.name ? 'New file' : block.name
|
|
20
|
+
return {
|
|
21
|
+
id: block.id,
|
|
22
|
+
name,
|
|
23
|
+
path: block.path || block.id,
|
|
24
|
+
folder: block.folder,
|
|
25
|
+
lines: 12,
|
|
26
|
+
language: languageOfName(name),
|
|
27
|
+
symbols: [],
|
|
28
|
+
imports: [],
|
|
29
|
+
userCreated: true,
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function namedCreatedBlocks(blocks: UserCreatedBlock[]) {
|
|
34
|
+
return blocks.filter((block) => !block.naming && Boolean(block.name))
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function namedCreatedIslands(islands: UserCreatedIsland[]) {
|
|
38
|
+
return islands.filter((island) => !island.naming && Boolean(island.name))
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function parseUserCreatedBlocks(value: unknown): UserCreatedBlock[] {
|
|
42
|
+
if (!Array.isArray(value)) return []
|
|
43
|
+
return value.flatMap((item) => {
|
|
44
|
+
if (!item || typeof item !== 'object') return []
|
|
45
|
+
const block = item as Partial<UserCreatedBlock>
|
|
46
|
+
if (
|
|
47
|
+
typeof block.id !== 'string' ||
|
|
48
|
+
typeof block.name !== 'string' ||
|
|
49
|
+
typeof block.path !== 'string' ||
|
|
50
|
+
typeof block.folder !== 'string' ||
|
|
51
|
+
typeof block.x !== 'number' ||
|
|
52
|
+
typeof block.z !== 'number'
|
|
53
|
+
) {
|
|
54
|
+
return []
|
|
55
|
+
}
|
|
56
|
+
return [
|
|
57
|
+
{
|
|
58
|
+
id: block.id,
|
|
59
|
+
name: block.name,
|
|
60
|
+
path: block.path,
|
|
61
|
+
folder: block.folder,
|
|
62
|
+
x: block.x,
|
|
63
|
+
z: block.z,
|
|
64
|
+
},
|
|
65
|
+
]
|
|
66
|
+
})
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function parseUserCreatedIslands(value: unknown): UserCreatedIsland[] {
|
|
70
|
+
if (!Array.isArray(value)) return []
|
|
71
|
+
return value.flatMap((item) => {
|
|
72
|
+
if (!item || typeof item !== 'object') return []
|
|
73
|
+
const island = item as Partial<UserCreatedIsland>
|
|
74
|
+
if (
|
|
75
|
+
typeof island.id !== 'string' ||
|
|
76
|
+
typeof island.name !== 'string' ||
|
|
77
|
+
typeof island.path !== 'string' ||
|
|
78
|
+
typeof island.parent !== 'string'
|
|
79
|
+
) {
|
|
80
|
+
return []
|
|
81
|
+
}
|
|
82
|
+
return [
|
|
83
|
+
{
|
|
84
|
+
id: island.id,
|
|
85
|
+
name: island.name,
|
|
86
|
+
path: island.path,
|
|
87
|
+
parent: island.parent,
|
|
88
|
+
},
|
|
89
|
+
]
|
|
90
|
+
})
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function resolveCreatedFile(rawName: string, folder: string) {
|
|
94
|
+
const trimmed = rawName.trim().replaceAll('\\', '/').replace(/^\.\//, '')
|
|
95
|
+
if (!trimmed) return null
|
|
96
|
+
const path = trimmed.includes('/')
|
|
97
|
+
? trimmed.replace(/^\/+/, '').replace(/\/+/g, '/')
|
|
98
|
+
: folder === '.'
|
|
99
|
+
? trimmed
|
|
100
|
+
: `${folder}/${trimmed}`
|
|
101
|
+
const name = path.split('/').pop() ?? path
|
|
102
|
+
if (!name) return null
|
|
103
|
+
return {
|
|
104
|
+
id: path,
|
|
105
|
+
name,
|
|
106
|
+
path,
|
|
107
|
+
folder: folderOfFile(path),
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function resolveCreatedIsland(rawName: string, parent: string) {
|
|
112
|
+
const trimmed = rawName
|
|
113
|
+
.trim()
|
|
114
|
+
.replaceAll('\\', '/')
|
|
115
|
+
.replace(/^\.\//, '')
|
|
116
|
+
.replace(/\/+$/, '')
|
|
117
|
+
if (!trimmed) return null
|
|
118
|
+
const path = trimmed.includes('/')
|
|
119
|
+
? trimmed.replace(/^\/+/, '').replace(/\/+/g, '/')
|
|
120
|
+
: parent === '.'
|
|
121
|
+
? trimmed
|
|
122
|
+
: `${parent}/${trimmed}`
|
|
123
|
+
const name = path.split('/').pop() ?? path
|
|
124
|
+
if (!name) return null
|
|
125
|
+
return {
|
|
126
|
+
id: path,
|
|
127
|
+
name,
|
|
128
|
+
path,
|
|
129
|
+
parent: folderParent(path) ?? '.',
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function islandKey(island: UserCreatedIsland) {
|
|
134
|
+
return island.path || island.id
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function islandWidth() {
|
|
138
|
+
const fileOffset = CONFIG.aisleWidth / 2 + CONFIG.fileWidth / 2 + 0.7
|
|
139
|
+
const fileOuter = fileOffset + CONFIG.fileWidth / 2
|
|
140
|
+
return (fileOuter + 1.1) * 2
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function islandDepth() {
|
|
144
|
+
return CONFIG.areaPadding * 2 + CONFIG.fileSpacing
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export function withUserCreatedGraph(
|
|
148
|
+
graph: CodebaseGraph,
|
|
149
|
+
blocks: UserCreatedBlock[],
|
|
150
|
+
islands: UserCreatedIsland[] = [],
|
|
151
|
+
): CodebaseGraph {
|
|
152
|
+
if (blocks.length === 0 && islands.length === 0) return graph
|
|
153
|
+
const files = new Map(graph.files.map((file) => [file.id, file]))
|
|
154
|
+
const folders = new Map(
|
|
155
|
+
graph.folders.map((folder) => [
|
|
156
|
+
folder.path,
|
|
157
|
+
{ ...folder, files: [...folder.files], children: [...folder.children] },
|
|
158
|
+
]),
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
for (const island of islands) {
|
|
162
|
+
const path = islandKey(island)
|
|
163
|
+
if (folders.has(path)) continue
|
|
164
|
+
const parent = island.parent || folderParent(path)
|
|
165
|
+
folders.set(path, {
|
|
166
|
+
path,
|
|
167
|
+
name: island.naming && !island.name ? 'New folder' : island.name,
|
|
168
|
+
parent,
|
|
169
|
+
files: [],
|
|
170
|
+
children: [],
|
|
171
|
+
userCreated: true,
|
|
172
|
+
})
|
|
173
|
+
if (parent) {
|
|
174
|
+
const parentFolder = folders.get(parent)
|
|
175
|
+
if (parentFolder && !parentFolder.children.includes(path)) {
|
|
176
|
+
parentFolder.children.push(path)
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
for (const block of blocks) {
|
|
182
|
+
if (files.has(block.id)) continue
|
|
183
|
+
const file = toCreatedFile(block)
|
|
184
|
+
files.set(file.id, file)
|
|
185
|
+
const folder = folders.get(file.folder)
|
|
186
|
+
if (folder && !folder.files.includes(file.id)) folder.files.push(file.id)
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return {
|
|
190
|
+
...graph,
|
|
191
|
+
files: [...files.values()],
|
|
192
|
+
folders: [...folders.values()],
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function overlayIslands(
|
|
197
|
+
layout: WorldLayout,
|
|
198
|
+
islands: UserCreatedIsland[],
|
|
199
|
+
): WorldLayout {
|
|
200
|
+
if (islands.length === 0) return layout
|
|
201
|
+
const folders = { ...layout.folders }
|
|
202
|
+
const bridges = [...layout.bridges]
|
|
203
|
+
const width = islandWidth()
|
|
204
|
+
const depth = islandDepth()
|
|
205
|
+
|
|
206
|
+
for (const island of islands) {
|
|
207
|
+
const id = islandKey(island)
|
|
208
|
+
if (layout.folders[id]) {
|
|
209
|
+
folders[id] = { ...folders[id], added: true, name: island.name || folders[id].name }
|
|
210
|
+
continue
|
|
211
|
+
}
|
|
212
|
+
const parentPath = island.parent
|
|
213
|
+
const parent = folders[parentPath]
|
|
214
|
+
if (!parent) continue
|
|
215
|
+
const siblings = Object.values(folders).filter((folder) => {
|
|
216
|
+
if (folder.path === id) return false
|
|
217
|
+
const placedIsland = islands.find((item) => islandKey(item) === folder.path)
|
|
218
|
+
const folderParentPath = placedIsland?.parent ?? folderParent(folder.path)
|
|
219
|
+
return folderParentPath === parentPath
|
|
220
|
+
})
|
|
221
|
+
const x =
|
|
222
|
+
siblings.length === 0
|
|
223
|
+
? parent.x
|
|
224
|
+
: Math.max(...siblings.map((folder) => folder.x + folder.width / 2)) +
|
|
225
|
+
CONFIG.siblingGap +
|
|
226
|
+
width / 2
|
|
227
|
+
const z = parent.z + parent.depth + CONFIG.bridgeLength
|
|
228
|
+
folders[id] = {
|
|
229
|
+
path: id,
|
|
230
|
+
name: island.naming && !island.name ? 'New folder' : island.name,
|
|
231
|
+
x,
|
|
232
|
+
z,
|
|
233
|
+
width,
|
|
234
|
+
depth,
|
|
235
|
+
added: true,
|
|
236
|
+
}
|
|
237
|
+
bridges.push({
|
|
238
|
+
id: `${parentPath}→${id}`,
|
|
239
|
+
label: folders[id].name,
|
|
240
|
+
fromLabel: parent.name,
|
|
241
|
+
points: [
|
|
242
|
+
[x, parent.z + parent.depth - CONFIG.bridgeOverlap],
|
|
243
|
+
[x, z + CONFIG.bridgeOverlap],
|
|
244
|
+
],
|
|
245
|
+
})
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
return { ...layout, folders, bridges }
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
export function withUserCreatedLayout(
|
|
252
|
+
layout: WorldLayout,
|
|
253
|
+
blocks: UserCreatedBlock[],
|
|
254
|
+
islands: UserCreatedIsland[] = [],
|
|
255
|
+
): WorldLayout {
|
|
256
|
+
const withIslands = overlayIslands(layout, islands)
|
|
257
|
+
if (blocks.length === 0) return withIslands
|
|
258
|
+
const files = { ...withIslands.files }
|
|
259
|
+
const height = fileHeight(12)
|
|
260
|
+
for (const block of blocks) {
|
|
261
|
+
const folder = withIslands.folders[block.folder]
|
|
262
|
+
files[block.id] = {
|
|
263
|
+
id: block.id,
|
|
264
|
+
position: [block.x, height / 2, block.z],
|
|
265
|
+
size: [CONFIG.fileWidth, height, CONFIG.fileDepth],
|
|
266
|
+
aisleFace: folder && block.x >= folder.x ? -1 : 1,
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
return { ...withIslands, files }
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
export function defaultBlockSpot(
|
|
273
|
+
layout: WorldLayout,
|
|
274
|
+
folderPath: string,
|
|
275
|
+
fileIndex: number,
|
|
276
|
+
): { x: number; z: number; folder: string } | null {
|
|
277
|
+
const folder = layout.folders[folderPath]
|
|
278
|
+
if (!folder) return null
|
|
279
|
+
const side: 1 | -1 = fileIndex % 2 === 0 ? -1 : 1
|
|
280
|
+
const row = Math.floor(fileIndex / 2)
|
|
281
|
+
return {
|
|
282
|
+
x: folder.x + side * (CONFIG.aisleWidth / 2 + CONFIG.fileWidth / 2 + 0.7),
|
|
283
|
+
z: folder.z + CONFIG.areaPadding + row * CONFIG.fileSpacing,
|
|
284
|
+
folder: folderPath,
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
export function isBlueprintSymbolName(value: string) {
|
|
289
|
+
return /^[A-Za-z_$][\w$]*$/.test(value.trim())
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
export function parseBlueprintImport(
|
|
293
|
+
raw: string,
|
|
294
|
+
file: string,
|
|
295
|
+
knownFileIds: Iterable<string> = [],
|
|
296
|
+
): PatchImportAddition | null {
|
|
297
|
+
const trimmed = raw.trim().replaceAll('\\', '/')
|
|
298
|
+
if (!trimmed || !file) return null
|
|
299
|
+
const match = trimmed.match(/^(.+?)\s+from\s+(.+)$/i)
|
|
300
|
+
const name = (match?.[1] ?? trimmed).trim()
|
|
301
|
+
const specifier = (match?.[2] ?? trimmed).trim()
|
|
302
|
+
if (!name || !specifier) return null
|
|
303
|
+
const known = new Set(knownFileIds)
|
|
304
|
+
const from =
|
|
305
|
+
known.has(specifier)
|
|
306
|
+
? specifier
|
|
307
|
+
: ([...known].find(
|
|
308
|
+
(id) => id === specifier || id.endsWith(`/${specifier}`),
|
|
309
|
+
) ?? specifier)
|
|
310
|
+
return { name, from, file }
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
export function withBlueprintIntent(
|
|
314
|
+
graph: CodebaseGraph,
|
|
315
|
+
functions: PatchSymbolAddition[] = [],
|
|
316
|
+
variables: PatchSymbolAddition[] = [],
|
|
317
|
+
imports: PatchImportAddition[] = [],
|
|
318
|
+
): CodebaseGraph {
|
|
319
|
+
if (functions.length === 0 && variables.length === 0 && imports.length === 0) {
|
|
320
|
+
return graph
|
|
321
|
+
}
|
|
322
|
+
const files = graph.files.map((file) => ({
|
|
323
|
+
...file,
|
|
324
|
+
symbols: [...file.symbols],
|
|
325
|
+
imports: [...file.imports],
|
|
326
|
+
}))
|
|
327
|
+
const byId = new Map(files.map((file) => [file.id, file]))
|
|
328
|
+
const known = new Set(byId.keys())
|
|
329
|
+
|
|
330
|
+
for (const item of functions) {
|
|
331
|
+
const file = byId.get(item.file)
|
|
332
|
+
if (!file) continue
|
|
333
|
+
if (file.symbols.some((symbol) => symbol.kind === 'function' && symbol.name === item.name)) {
|
|
334
|
+
continue
|
|
335
|
+
}
|
|
336
|
+
file.symbols.push({ name: item.name, kind: 'function', intended: true })
|
|
337
|
+
}
|
|
338
|
+
for (const item of variables) {
|
|
339
|
+
const file = byId.get(item.file)
|
|
340
|
+
if (!file) continue
|
|
341
|
+
if (file.symbols.some((symbol) => symbol.kind === 'variable' && symbol.name === item.name)) {
|
|
342
|
+
continue
|
|
343
|
+
}
|
|
344
|
+
file.symbols.push({ name: item.name, kind: 'variable', intended: true })
|
|
345
|
+
}
|
|
346
|
+
for (const item of imports) {
|
|
347
|
+
const file = byId.get(item.file)
|
|
348
|
+
if (!file || !known.has(item.from) || file.imports.includes(item.from)) continue
|
|
349
|
+
file.imports.push(item.from)
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
return { ...graph, files }
|
|
353
|
+
}
|
|
354
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
/// <reference types="vite/client" />
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"useDefineForClassFields": true,
|
|
5
|
+
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
|
6
|
+
"module": "ESNext",
|
|
7
|
+
"skipLibCheck": true,
|
|
8
|
+
"moduleResolution": "bundler",
|
|
9
|
+
"allowImportingTsExtensions": true,
|
|
10
|
+
"resolveJsonModule": true,
|
|
11
|
+
"isolatedModules": true,
|
|
12
|
+
"moduleDetection": "force",
|
|
13
|
+
"noEmit": true,
|
|
14
|
+
"jsx": "react-jsx",
|
|
15
|
+
"strict": true,
|
|
16
|
+
"noUnusedLocals": true,
|
|
17
|
+
"noUnusedParameters": true,
|
|
18
|
+
"noFallthroughCasesInSwitch": true
|
|
19
|
+
},
|
|
20
|
+
"include": ["src"]
|
|
21
|
+
}
|