@forge-kit/plugin-source-map-prase 0.0.1 → 0.0.3

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 (37) hide show
  1. package/dist/app.js +3 -5
  2. package/dist/node.js +2556 -0
  3. package/package.json +5 -3
  4. package/src/App.less +43 -74
  5. package/src/App.tsx +98 -54
  6. package/src/components/map-input-panel/index.less +71 -79
  7. package/src/components/map-input-panel/index.tsx +65 -69
  8. package/src/components/panel-card/index.less +21 -0
  9. package/src/components/panel-card/index.tsx +6 -8
  10. package/src/components/trace-chain/index.less +126 -125
  11. package/src/components/trace-chain/index.tsx +25 -24
  12. package/src/main.tsx +1 -1
  13. package/src/node/index.ts +6 -0
  14. package/src/{utils/source-map → node/trace/core}/base/registry.ts +14 -11
  15. package/src/{utils/source-map → node/trace/core}/base/types.ts +1 -0
  16. package/src/node/trace/core/domain/chain-slots.ts +33 -0
  17. package/src/{utils/source-map → node/trace/core}/domain/source-content.ts +15 -2
  18. package/src/node/trace/core/domain/trace-resolver.ts +179 -0
  19. package/src/node/trace/core/domain/view-model.ts +57 -0
  20. package/src/node/trace/resolve/context.ts +59 -0
  21. package/src/node/trace/resolve/index.ts +97 -0
  22. package/src/node/trace/resolve/input.ts +35 -0
  23. package/src/node/trace/resolve/snippet-limit.ts +35 -0
  24. package/src/node/trace/resolve/types.ts +37 -0
  25. package/src/node/trace/runner.ts +149 -0
  26. package/src/shared/trace-common.ts +104 -0
  27. package/src/shared/trace-contract.ts +29 -0
  28. package/src/types.ts +19 -0
  29. package/src/utils/trace-ui/index.ts +12 -0
  30. package/src/utils/trace-ui/state.ts +81 -0
  31. package/src/utils/source-map/domain/chain-slots.ts +0 -59
  32. package/src/utils/source-map/domain/trace-resolver.ts +0 -165
  33. package/src/utils/source-map/domain/view-model.ts +0 -20
  34. package/src/utils/source-map/facade/source-map-utils.ts +0 -212
  35. package/src/utils/source-map/index.ts +0 -18
  36. /package/src/{utils/source-map → node/trace/core}/base/constants.ts +0 -0
  37. /package/src/{utils/source-map → node/trace/core}/base/path.ts +0 -0
package/package.json CHANGED
@@ -6,11 +6,12 @@
6
6
  },
7
7
  "files": [
8
8
  "dist",
9
- "src"
9
+ "src",
10
+ "manifest.json"
10
11
  ],
11
12
  "author": "yu.pan <panyupy@vip.qq.com>",
12
13
  "license": "MIT",
13
- "version": "0.0.1",
14
+ "version": "0.0.3",
14
15
  "type": "module",
15
16
  "scripts": {
16
17
  "dev": "vite",
@@ -24,7 +25,8 @@
24
25
  "@dnd-kit/utilities": "^3.2.2",
25
26
  "@forge-kit/component": "workspace:*",
26
27
  "@forge-kit/icons": "workspace:*",
27
- "@forge-kit/types": "workspace:*",
28
+ "@forge-kit/helper": "workspace:*",
29
+ "@forge-kit/node-sdk": "workspace:*",
28
30
  "classnames": "^2.5.1",
29
31
  "react": "^19.2.0",
30
32
  "react-dom": "^19.2.0",
package/src/App.less CHANGED
@@ -1,5 +1,4 @@
1
1
  html, body, #root {
2
- -webkit-app-region: drag;
3
2
  background-color: transparent;
4
3
  margin: 0;
5
4
  height: 100%;
@@ -16,86 +15,56 @@ html, body, #root {
16
15
  height: 100%;
17
16
  overflow: hidden;
18
17
  min-height: 0;
19
- }
20
-
21
- .top-cards {
22
- flex: 1;
23
- min-height: 0;
24
- }
25
-
26
- .trace-panel-card,
27
- .input-panel-card,
28
- .result-panel-card {
29
-
30
- -webkit-app-region: no-drag;
31
- border: 1px solid color-mix(in srgb, var(--gray-6) 75%, transparent);
32
- background:
33
- linear-gradient(180deg, color-mix(in srgb, var(--gray-2) 88%, transparent), color-mix(in srgb, var(--gray-1) 86%, transparent));
34
- box-shadow:
35
- inset 0 1px 0 rgba(255, 255, 255, 0.04),
36
- 0 10px 24px rgba(0, 0, 0, 0.16);
37
- }
38
-
39
- .input-panel-card,
40
- .trace-panel-card {
41
- flex: 1;
42
- min-height: 0;
43
- }
44
-
45
- .trace-panel-card {
46
- display: flex;
47
- }
48
18
 
49
- .result-panel-card {
50
- flex: 1;
51
- min-height: 0;
52
- display: flex;
53
- }
19
+ .top-cards {
20
+ flex: 1;
21
+ min-height: 0;
54
22
 
55
- .panel-shell {
56
- flex: 1;
57
- min-height: 0;
58
- }
23
+ .top-trace-card {
24
+ flex: 1;
25
+ min-height: 0;
26
+ }
59
27
 
60
- .panel-title {
61
- display: block;
62
- padding: 0 0 var(--space-3);
63
- margin-bottom: var(--space-2);
64
- border-bottom: solid 1px var(--gray-5);
65
- font-size: 12px;
66
- letter-spacing: 0.1em;
67
- color: var(--gray-11);
68
- text-transform: uppercase;
69
- }
28
+ .top-input-card {
29
+ flex: 1;
30
+ min-height: 0;
31
+ }
32
+ }
70
33
 
71
- .trace-panel-content,
72
- .result-panel-content {
73
- padding-top: var(--space-3);
74
- flex: 1;
75
- min-height: 0;
76
- }
34
+ .result-panel-card {
35
+ flex: 1;
36
+ min-height: 0;
37
+ display: flex;
77
38
 
78
- .trace-panel-content {
79
- overflow: hidden;
80
- }
39
+ .result-panel-content {
40
+ flex: 1;
41
+ min-height: 0;
42
+ width: 100%;
43
+ overflow-y: auto;
44
+ overflow-x: hidden;
81
45
 
82
- .result-panel-content {
83
- overflow: auto;
84
- }
46
+ .output-meta {
47
+ margin-bottom: var(--space-2);
48
+ color: var(--gray-11);
49
+ font-size: 12px;
50
+ border: 1px solid var(--gray-6);
51
+ border-radius: 999px;
52
+ padding: 2px 10px;
53
+ }
85
54
 
86
- .trace-chain-root {
87
- height: 100%;
88
- }
55
+ .code-scroll-wrap {
56
+ width: 100%;
57
+ max-width: 100%;
58
+ min-width: 0;
59
+ overflow: auto;
89
60
 
90
- .output-meta {
91
- display: inline-block;
92
- margin-bottom: var(--space-2);
93
- color: var(--gray-11);
94
- font-size: 12px;
95
- border: 1px solid var(--gray-6);
96
- border-radius: 999px;
97
- padding: 2px 10px;
98
- background: color-mix(in srgb, var(--gray-3) 75%, transparent);
99
- -webkit-app-region: no-drag;
61
+ .code-highlight-view {
62
+ width: max-content;
63
+ min-width: 100%;
64
+ box-sizing: border-box;
65
+ }
66
+ }
67
+ }
68
+ }
100
69
  }
101
70
  }
package/src/App.tsx CHANGED
@@ -4,12 +4,18 @@ import {CodeHighlight} from "@/components/code-highlight";
4
4
  import {MapInputPanel} from "@/components/map-input-panel";
5
5
  import {PanelCard} from "@/components/panel-card";
6
6
  import {TraceChain} from "@/components/trace-chain";
7
+ import type {
8
+ ForgeKitBridge,
9
+ MapInputConfig,
10
+ ResolveTraceMetaOutput,
11
+ ResolveTraceSnippetOutput,
12
+ } from "@/types";
7
13
  import {
8
14
  SourceMapUtils,
9
15
  type ChainMapSlot,
10
16
  type SourceMapChainState,
11
17
  type TraceOutputState,
12
- } from "@/utils/source-map";
18
+ } from "@/utils/trace-ui";
13
19
  import '@forge-kit/component/style.css'
14
20
  import './App.less'
15
21
 
@@ -17,20 +23,21 @@ interface AppProps {
17
23
  themeProps?: React.ComponentProps<typeof Theme>
18
24
  }
19
25
 
20
- interface InputState {
21
- entryLine: string
22
- entryColumn: string
23
- contextLineRadius: string
26
+ const DEFAULT_MAX_SNIPPET_PAYLOAD_BYTES = String(32 * 1024)
27
+
28
+ const stripMapExt = (fileName: string) => fileName.replace(/\.map$/i, "")
29
+ const getMapFileNameFromPath = (filePath: string) => {
30
+ const normalized = filePath.replace(/\\/g, "/")
31
+ const segments = normalized.split("/")
32
+ return segments[segments.length - 1] || filePath
24
33
  }
25
34
 
26
35
  const initialChainState: SourceMapChainState = {
27
- sourceMaps: {},
28
- sourceMapFileNames: [],
29
36
  chainMapSlots: [],
30
37
  entryFileName: "",
31
38
  }
32
39
 
33
- const initialInputState: InputState = {
40
+ const initialInputState: MapInputConfig = {
34
41
  entryLine: "",
35
42
  entryColumn: "",
36
43
  contextLineRadius: String(SourceMapUtils.DEFAULT_CONTEXT_LINE_RADIUS),
@@ -39,24 +46,31 @@ const initialInputState: InputState = {
39
46
  export const App: React.FC<AppProps> = (props) => {
40
47
  const {themeProps} = props
41
48
  const [chainState, setChainState] = React.useState<SourceMapChainState>(initialChainState)
42
- const [inputState, setInputState] = React.useState<InputState>(initialInputState)
49
+ const [inputState, setInputState] = React.useState<MapInputConfig>(initialInputState)
43
50
  const [outputState, setOutputState] = React.useState<TraceOutputState>(SourceMapUtils.createInitialOutputState())
44
51
 
45
52
  const traceChainDisplayData = React.useMemo(() => {
46
53
  return SourceMapUtils.mapTraceForDisplay(outputState.traceData)
47
54
  }, [outputState.traceData])
48
55
 
49
- const applyChainSlots =(slots: ChainMapSlot[]) => {
50
- setChainState(SourceMapUtils.buildChainStateFromSlots(slots))
56
+ const applyChainSlots = (slots: ChainMapSlot[]) => {
57
+ setChainState({
58
+ chainMapSlots: slots,
59
+ entryFileName: slots.length ? stripMapExt(slots[0].mapFileName) : "",
60
+ })
51
61
  }
52
62
 
53
63
  const setOutputMessage = (message: string) => {
54
64
  setOutputState((prev) => SourceMapUtils.patchOutputMessage(prev, message))
55
65
  }
56
66
 
57
- const handleAppendChainFile = async (file: File) => {
58
- const slotFromFile = await SourceMapUtils.createChainSlotFromFile(file)
59
- applyChainSlots([...chainState.chainMapSlots, slotFromFile])
67
+ const handleAppendMapFilePath = (filePath: string) => {
68
+ const slot: ChainMapSlot = {
69
+ id: `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`,
70
+ mapFileName: getMapFileNameFromPath(filePath),
71
+ mapFilePath: filePath,
72
+ }
73
+ applyChainSlots([...chainState.chainMapSlots, slot])
60
74
  }
61
75
 
62
76
  const handleRemoveChainSlot = (slotId: string) => {
@@ -73,18 +87,52 @@ export const App: React.FC<AppProps> = (props) => {
73
87
  entryLine: inputState.entryLine,
74
88
  entryColumn: inputState.entryColumn,
75
89
  contextLineRadius: inputState.contextLineRadius,
76
- mapCount: chainState.sourceMapFileNames.length,
90
+ mapCount: chainState.chainMapSlots.length,
77
91
  })
78
92
 
79
93
  if (!validated.ok) return setOutputMessage(validated.message)
80
94
 
81
- const nextOutput = await SourceMapUtils.resolveTraceOutput({
82
- entry: validated.entry,
83
- contextLineRadius: validated.contextLineRadius,
84
- sourceMaps: chainState.sourceMaps,
85
- sourceMapFileNames: chainState.sourceMapFileNames,
86
- })
87
- setOutputState(nextOutput)
95
+ const applyNodeMethod = (window as Window & { forgeKit?: ForgeKitBridge }).forgeKit?.applyNodeMethod
96
+
97
+ const mapFilePaths = chainState.chainMapSlots
98
+ .map((slot) => slot.mapFilePath)
99
+ .filter((path): path is string => Boolean(path))
100
+
101
+ if (!applyNodeMethod) return setOutputMessage("Node bridge 不可用,无法执行解析。")
102
+
103
+ if (mapFilePaths.length === 0) return setOutputMessage("未提供 map 文件路径,无法在 Node 侧解析。")
104
+
105
+ const payload = {
106
+ mapFilePaths,
107
+ entryFileName: validated.entry.fileName,
108
+ entryLine: String(validated.entry.lineNumber),
109
+ entryColumn: String(validated.entry.columnNumber),
110
+ contextLineRadius: String(validated.contextLineRadius),
111
+ maxSnippetPayloadBytes: DEFAULT_MAX_SNIPPET_PAYLOAD_BYTES,
112
+ }
113
+
114
+ try {
115
+ const nextOutput = await applyNodeMethod('resolveTrace', JSON.stringify(payload)) as ResolveTraceMetaOutput
116
+ setOutputState((prev) => ({
117
+ ...prev,
118
+ traceData: nextOutput.traceData || [],
119
+ resolvedSourceMeta: nextOutput.resolvedSourceMeta || null,
120
+ traceCode: nextOutput.message || prev.traceCode,
121
+ traceHighlightLines: [],
122
+ }))
123
+ if (nextOutput?.resolvedSourceMeta) {
124
+ try {
125
+ const snippet = await applyNodeMethod('getSourceSnippet', JSON.stringify(payload)) as ResolveTraceSnippetOutput
126
+ setOutputState((prev) => ({...prev, ...snippet}))
127
+ } catch (error) {
128
+ const message = error instanceof Error ? error.message : String(error)
129
+ setOutputState((prev) => SourceMapUtils.patchOutputMessage(prev, `源码片段加载失败: ${message}`))
130
+ }
131
+ }
132
+ } catch (error) {
133
+ const message = error instanceof Error ? error.message : String(error)
134
+ setOutputMessage(`Node resolve failed: ${message}`)
135
+ }
88
136
  }
89
137
 
90
138
  return (
@@ -92,48 +140,44 @@ export const App: React.FC<AppProps> = (props) => {
92
140
  radius="full" panelBackground="translucent" {...themeProps}>
93
141
  <Flex className="forge-kit-plugin-source-map-prase-content" direction="column" gap="3">
94
142
  <Flex className="top-cards" gap="3" align="stretch">
95
- <PanelCard title="TRACE CHAIN" cardClassName="trace-panel-card" contentClassName="trace-panel-content">
96
- <TraceChain
97
- slots={chainState.chainMapSlots.map((slot) => ({
98
- id: slot.id,
99
- mapFileName: slot.mapFileName,
100
- error: slot.error,
101
- }))}
102
- data={traceChainDisplayData}
103
- onReorder={handleReorderChainSlots}
104
- onDelete={handleRemoveChainSlot}
105
- className="trace-chain-root"
106
- />
107
- </PanelCard>
143
+ <TraceChain
144
+ className="top-trace-card"
145
+ slots={chainState.chainMapSlots.map((slot) => ({
146
+ id: slot.id,
147
+ mapFileName: slot.mapFileName,
148
+ error: slot.error,
149
+ }))}
150
+ data={traceChainDisplayData}
151
+ onReorder={handleReorderChainSlots}
152
+ onDelete={handleRemoveChainSlot}
153
+ />
108
154
 
109
155
  <MapInputPanel
156
+ className="top-input-card"
110
157
  chainDepth={chainState.chainMapSlots.length}
111
158
  entryFileName={chainState.entryFileName}
112
159
  inputConfig={inputState}
113
160
  onInputConfigChange={setInputState}
114
161
  onResolve={handleResolveTrace}
115
- onAppendMapFile={handleAppendChainFile}
162
+ onAppendMapFilePath={handleAppendMapFilePath}
116
163
  />
117
164
  </Flex>
118
165
 
119
- <PanelCard
120
- title="TRACE OUTPUT"
121
- cardClassName="result-panel-card"
122
- contentClassName="result-panel-content"
123
- contentProps={{direction: "column"}}
124
- >
125
- {outputState.resolvedSourceMeta && (
126
- <Text className="output-meta">
127
- {`目标位置: ${outputState.resolvedSourceMeta.sourceFile}:${outputState.resolvedSourceMeta.lineNumber}:${outputState.resolvedSourceMeta.columnNumber}`}
128
- </Text>
129
- )}
130
- <Box>
131
- <CodeHighlight
132
- code={outputState.traceCode}
133
- highlightLines={outputState.traceHighlightLines}
134
- className="code-highlight-view"
135
- />
136
- </Box>
166
+ <PanelCard title="TRACE OUTPUT" className="result-panel-card">
167
+ <Flex className="result-panel-content" direction="column">
168
+ {outputState.resolvedSourceMeta && (
169
+ <Text className="output-meta">
170
+ {`目标位置: ${outputState.resolvedSourceMeta.sourceFile}:${outputState.resolvedSourceMeta.lineNumber}:${outputState.resolvedSourceMeta.columnNumber}`}
171
+ </Text>
172
+ )}
173
+ <Box className="code-scroll-wrap">
174
+ <CodeHighlight
175
+ code={outputState.traceCode}
176
+ highlightLines={outputState.traceHighlightLines}
177
+ className="code-highlight-view"
178
+ />
179
+ </Box>
180
+ </Flex>
137
181
  </PanelCard>
138
182
  </Flex>
139
183
  </Theme>
@@ -1,95 +1,87 @@
1
- .forge-kit-plugin-source-map-prase {
2
- .input-panel-card {
3
- flex: 1;
4
- display: flex;
1
+ .input-panel-card {
2
+ .input-panel-content {
3
+ overflow: auto;
4
+ gap: 10px;
5
5
 
6
- .input-panel-content {
7
- flex: 1;
8
- padding-top: var(--space-3);
9
- overflow: auto;
10
- gap: 10px;
11
- }
12
-
13
- .input-footer {
6
+ .input-summary-grid {
14
7
  display: grid;
15
- grid-template-rows: auto minmax(0, 1fr);
16
- row-gap: 10px;
17
- border-top: 1px solid var(--gray-5);
18
- padding-top: 10px;
19
- flex: 1;
20
- }
21
- }
8
+ grid-template-columns: repeat(2, minmax(0, 1fr));
9
+ gap: 8px;
22
10
 
23
- .input-summary-grid {
24
- display: grid;
25
- grid-template-columns: repeat(2, minmax(0, 1fr));
26
- gap: 8px;
27
- }
11
+ .input-summary-item {
12
+ border: 1px solid var(--gray-5);
13
+ border-radius: var(--radius-3);
14
+ padding: 8px 10px;
15
+ background: var(--gray-2);
28
16
 
29
- .input-summary-item {
30
- border: 1px solid var(--gray-5);
31
- border-radius: var(--radius-3);
32
- padding: 8px 10px;
33
- background: var(--gray-2);
34
- }
17
+ .input-summary-label {
18
+ display: block;
19
+ color: var(--gray-10);
20
+ font-size: 12px;
21
+ margin-bottom: 4px;
22
+ }
35
23
 
36
- .input-summary-label {
37
- display: block;
38
- color: var(--gray-10);
39
- font-size: 12px;
40
- margin-bottom: 4px;
41
- }
24
+ .input-summary-value {
25
+ display: block;
26
+ color: var(--gray-12);
27
+ font-size: 13px;
28
+ font-weight: 600;
29
+ word-break: break-all;
30
+ }
31
+ }
32
+ }
42
33
 
43
- .input-summary-value {
44
- display: block;
45
- color: var(--gray-12);
46
- font-size: 13px;
47
- font-weight: 600;
48
- word-break: break-all;
49
- }
34
+ .input-hints {
35
+ border: 1px dashed var(--gray-6);
36
+ border-radius: var(--radius-3);
37
+ padding: 8px 10px;
38
+ background: color-mix(in srgb, var(--gray-2) 70%, transparent);
50
39
 
51
- .input-hints {
52
- border: 1px dashed var(--gray-6);
53
- border-radius: var(--radius-3);
54
- padding: 8px 10px;
55
- background: color-mix(in srgb, var(--gray-2) 70%, transparent);
56
- }
40
+ .input-hints-title {
41
+ display: block;
42
+ font-size: 12px;
43
+ font-weight: 600;
44
+ color: var(--gray-11);
45
+ margin-bottom: 6px;
46
+ }
57
47
 
58
- .input-hints-title {
59
- display: block;
60
- font-size: 12px;
61
- font-weight: 600;
62
- color: var(--gray-11);
63
- margin-bottom: 6px;
64
- }
48
+ .input-hints-item {
49
+ display: block;
50
+ font-size: 12px;
51
+ line-height: 1.6;
52
+ color: var(--gray-11);
53
+ }
54
+ }
65
55
 
66
- .input-hints-item {
67
- display: block;
68
- font-size: 12px;
69
- line-height: 1.6;
70
- color: var(--gray-11);
71
- }
56
+ .input-footer {
57
+ display: grid;
58
+ grid-template-rows: auto minmax(0, 1fr);
59
+ row-gap: 10px;
60
+ border-top: 1px solid var(--gray-5);
61
+ padding-top: 10px;
72
62
 
73
- .field-row-compact {
74
- flex: 1;
75
- }
63
+ .entry-row {
64
+ .field-row-compact {
65
+ flex: 1;
76
66
 
77
- .entry-actions {
78
- display: flex;
79
- flex: 1;
80
- }
67
+ .field-label {
68
+ color: var(--gray-11);
69
+ font-size: 12px;
70
+ }
81
71
 
82
- .entry-action-row {
83
- width: auto;
84
- }
72
+ .rt-TextFieldRoot {
73
+ border-radius: 6px;
74
+ }
75
+ }
76
+ }
85
77
 
86
- .field-label {
87
- color: var(--gray-11);
88
- font-size: 12px;
89
- }
78
+ .entry-actions {
79
+ display: flex;
90
80
 
91
- .rt-TextFieldRoot {
92
- border-radius: 6px;
81
+ .entry-action-row {
82
+ width: auto;
83
+ }
84
+ }
85
+ }
93
86
  }
94
-
95
87
  }