@forge-kit/plugin-source-map-prase 0.0.2 → 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 +35 -26
  5. package/src/App.tsx +72 -23
  6. package/src/components/map-input-panel/index.less +71 -78
  7. package/src/components/map-input-panel/index.tsx +19 -20
  8. package/src/components/panel-card/index.less +16 -13
  9. package/src/components/panel-card/index.tsx +2 -2
  10. package/src/components/trace-chain/index.less +119 -120
  11. package/src/components/trace-chain/index.tsx +9 -7
  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.2",
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
@@ -16,46 +16,55 @@ html, body, #root {
16
16
  overflow: hidden;
17
17
  min-height: 0;
18
18
 
19
-
20
19
  .top-cards {
21
20
  flex: 1;
22
21
  min-height: 0;
22
+
23
+ .top-trace-card {
24
+ flex: 1;
25
+ min-height: 0;
26
+ }
27
+
28
+ .top-input-card {
29
+ flex: 1;
30
+ min-height: 0;
31
+ }
23
32
  }
24
33
 
25
34
  .result-panel-card {
26
35
  flex: 1;
27
36
  min-height: 0;
28
37
  display: flex;
29
- }
30
38
 
31
- .result-panel-content {
32
- flex: 1;
33
- min-height: 0;
34
- width: 100%;
35
- overflow-y: auto;
36
- overflow-x: hidden;
37
- }
39
+ .result-panel-content {
40
+ flex: 1;
41
+ min-height: 0;
42
+ width: 100%;
43
+ overflow-y: auto;
44
+ overflow-x: hidden;
38
45
 
39
- .output-meta {
40
- margin-bottom: var(--space-2);
41
- color: var(--gray-11);
42
- font-size: 12px;
43
- border: 1px solid var(--gray-6);
44
- border-radius: 999px;
45
- padding: 2px 10px;
46
- }
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
+ }
47
54
 
48
- .code-scroll-wrap {
49
- width: 100%;
50
- max-width: 100%;
51
- min-width: 0;
52
- overflow-x: auto;
53
- overflow-y: hidden;
55
+ .code-scroll-wrap {
56
+ width: 100%;
57
+ max-width: 100%;
58
+ min-width: 0;
59
+ overflow: auto;
54
60
 
55
- .code-highlight-view {
56
- width: max-content;
61
+ .code-highlight-view {
62
+ width: max-content;
63
+ min-width: 100%;
64
+ box-sizing: border-box;
65
+ }
66
+ }
57
67
  }
58
68
  }
59
69
  }
60
-
61
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,7 +46,7 @@ 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(() => {
@@ -47,16 +54,23 @@ export const App: React.FC<AppProps> = (props) => {
47
54
  }, [outputState.traceData])
48
55
 
49
56
  const applyChainSlots = (slots: ChainMapSlot[]) => {
50
- setChainState(SourceMapUtils.buildChainStateFromSlots(slots))
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 (
@@ -93,6 +141,7 @@ export const App: React.FC<AppProps> = (props) => {
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
143
  <TraceChain
144
+ className="top-trace-card"
96
145
  slots={chainState.chainMapSlots.map((slot) => ({
97
146
  id: slot.id,
98
147
  mapFileName: slot.mapFileName,
@@ -101,16 +150,16 @@ export const App: React.FC<AppProps> = (props) => {
101
150
  data={traceChainDisplayData}
102
151
  onReorder={handleReorderChainSlots}
103
152
  onDelete={handleRemoveChainSlot}
104
- className="trace-chain-root"
105
153
  />
106
154
 
107
155
  <MapInputPanel
156
+ className="top-input-card"
108
157
  chainDepth={chainState.chainMapSlots.length}
109
158
  entryFileName={chainState.entryFileName}
110
159
  inputConfig={inputState}
111
160
  onInputConfigChange={setInputState}
112
161
  onResolve={handleResolveTrace}
113
- onAppendMapFile={handleAppendChainFile}
162
+ onAppendMapFilePath={handleAppendMapFilePath}
114
163
  />
115
164
  </Flex>
116
165
 
@@ -1,94 +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
- overflow: auto;
9
- gap: 10px;
10
- }
11
-
12
- .input-footer {
6
+ .input-summary-grid {
13
7
  display: grid;
14
- grid-template-rows: auto minmax(0, 1fr);
15
- row-gap: 10px;
16
- border-top: 1px solid var(--gray-5);
17
- padding-top: 10px;
18
- flex: 1;
19
- }
20
- }
8
+ grid-template-columns: repeat(2, minmax(0, 1fr));
9
+ gap: 8px;
21
10
 
22
- .input-summary-grid {
23
- display: grid;
24
- grid-template-columns: repeat(2, minmax(0, 1fr));
25
- gap: 8px;
26
- }
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);
27
16
 
28
- .input-summary-item {
29
- border: 1px solid var(--gray-5);
30
- border-radius: var(--radius-3);
31
- padding: 8px 10px;
32
- background: var(--gray-2);
33
- }
17
+ .input-summary-label {
18
+ display: block;
19
+ color: var(--gray-10);
20
+ font-size: 12px;
21
+ margin-bottom: 4px;
22
+ }
34
23
 
35
- .input-summary-label {
36
- display: block;
37
- color: var(--gray-10);
38
- font-size: 12px;
39
- margin-bottom: 4px;
40
- }
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
+ }
41
33
 
42
- .input-summary-value {
43
- display: block;
44
- color: var(--gray-12);
45
- font-size: 13px;
46
- font-weight: 600;
47
- word-break: break-all;
48
- }
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);
49
39
 
50
- .input-hints {
51
- border: 1px dashed var(--gray-6);
52
- border-radius: var(--radius-3);
53
- padding: 8px 10px;
54
- background: color-mix(in srgb, var(--gray-2) 70%, transparent);
55
- }
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
+ }
56
47
 
57
- .input-hints-title {
58
- display: block;
59
- font-size: 12px;
60
- font-weight: 600;
61
- color: var(--gray-11);
62
- margin-bottom: 6px;
63
- }
48
+ .input-hints-item {
49
+ display: block;
50
+ font-size: 12px;
51
+ line-height: 1.6;
52
+ color: var(--gray-11);
53
+ }
54
+ }
64
55
 
65
- .input-hints-item {
66
- display: block;
67
- font-size: 12px;
68
- line-height: 1.6;
69
- color: var(--gray-11);
70
- }
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;
71
62
 
72
- .field-row-compact {
73
- flex: 1;
74
- }
63
+ .entry-row {
64
+ .field-row-compact {
65
+ flex: 1;
75
66
 
76
- .entry-actions {
77
- display: flex;
78
- flex: 1;
79
- }
67
+ .field-label {
68
+ color: var(--gray-11);
69
+ font-size: 12px;
70
+ }
80
71
 
81
- .entry-action-row {
82
- width: auto;
83
- }
72
+ .rt-TextFieldRoot {
73
+ border-radius: 6px;
74
+ }
75
+ }
76
+ }
84
77
 
85
- .field-label {
86
- color: var(--gray-11);
87
- font-size: 12px;
88
- }
78
+ .entry-actions {
79
+ display: flex;
89
80
 
90
- .rt-TextFieldRoot {
91
- border-radius: 6px;
81
+ .entry-action-row {
82
+ width: auto;
83
+ }
84
+ }
85
+ }
92
86
  }
93
-
94
87
  }
@@ -1,22 +1,19 @@
1
1
  import * as React from "react";
2
2
  import {Box, Button, Flex, Text, TextField} from "@forge-kit/component";
3
3
  import {PanelCard} from "@/components/panel-card";
4
+ import type {ForgeKitBridge, MapInputConfig} from "@/types";
4
5
  import {noop} from "@/utils";
6
+ import classNames from "classnames";
5
7
  import "./index.less";
6
8
 
7
- interface MapInputConfig {
8
- entryLine: string
9
- entryColumn: string
10
- contextLineRadius: string
11
- }
12
-
13
9
  interface MapInputPanelProps {
10
+ className?: string
14
11
  chainDepth: number
15
12
  entryFileName: string
16
13
  inputConfig: MapInputConfig
17
14
  onInputConfigChange: (next: MapInputConfig) => void
18
15
  onResolve: () => void
19
- onAppendMapFile: (file: File) => void
16
+ onAppendMapFilePath: (filePath: string) => void
20
17
  }
21
18
 
22
19
  interface MapInputFieldProps {
@@ -45,25 +42,27 @@ const MapInputField: React.FC<MapInputFieldProps> = (props) => {
45
42
 
46
43
  export const MapInputPanel: React.FC<MapInputPanelProps> = (props) => {
47
44
  const {
45
+ className,
48
46
  chainDepth,
49
47
  entryFileName,
50
48
  inputConfig,
51
49
  onInputConfigChange,
52
50
  onResolve,
53
- onAppendMapFile,
51
+ onAppendMapFilePath,
54
52
  } = props
55
53
 
56
- const openFilePicker = () => {
57
- const fileInput = document.createElement("input")
58
- fileInput.type = "file"
59
- fileInput.accept = ".map,application/json"
60
- fileInput.onchange = () => {
61
- const file = fileInput.files?.[0]
62
- if (file) {
63
- onAppendMapFile(file)
64
- }
65
- }
66
- fileInput.click()
54
+ const openFilePicker = async () => {
55
+ const openFileDialog = (window as Window & {forgeKit?: ForgeKitBridge}).forgeKit?.openFileDialog
56
+ if (!openFileDialog) return
57
+
58
+ const selected = await openFileDialog({
59
+ multiple: true,
60
+ filters: [{name: "Source Map", extensions: ["map", "json"]}],
61
+ })
62
+ const paths = Array.isArray(selected) ? selected : (selected ? [selected] : [])
63
+ paths.forEach((filePath) => {
64
+ if (typeof filePath === "string") onAppendMapFilePath(filePath)
65
+ })
67
66
  }
68
67
 
69
68
  const handleInputConfigChange = (field: keyof MapInputConfig, value: string) => {
@@ -71,7 +70,7 @@ export const MapInputPanel: React.FC<MapInputPanelProps> = (props) => {
71
70
  }
72
71
 
73
72
  return (
74
- <PanelCard title="MAP INPUT" className="input-panel-card">
73
+ <PanelCard title="MAP INPUT" className={classNames("input-panel-card", className)}>
75
74
  <Flex className="input-panel-content" direction='column' gap="3">
76
75
  <Box className="input-summary-grid">
77
76
  <Box className="input-summary-item">
@@ -1,18 +1,21 @@
1
+ .panel-card {
2
+ display: flex !important;
1
3
 
2
- .panel-shell {
3
- overflow: hidden;
4
- width: 100%;
4
+ .panel-shell {
5
+ overflow: hidden;
6
+ flex: 1;
5
7
 
6
- .panel-title {
7
- display: block;
8
- padding: 0 0 var(--space-3);
9
- margin-bottom: var(--space-3);
10
- border-bottom: solid 1px var(--gray-5);
11
- font-size: 12px;
12
- letter-spacing: 0.1em;
13
- color: var(--gray-11);
14
- text-transform: uppercase;
8
+ .panel-title {
9
+ display: block;
10
+ padding: 0 0 var(--space-3);
11
+ margin-bottom: var(--space-3);
12
+ border-bottom: solid 1px var(--gray-5);
13
+ font-size: 12px;
14
+ letter-spacing: 0.1em;
15
+ color: var(--gray-11);
16
+ text-transform: uppercase;
17
+ }
15
18
  }
16
- }
17
19
 
20
+ }
18
21
 
@@ -5,7 +5,7 @@ import './index.less'
5
5
 
6
6
  interface PanelCardProps {
7
7
  title: string
8
- className: string
8
+ className?: string
9
9
  children?: React.ReactNode
10
10
  }
11
11
 
@@ -13,7 +13,7 @@ export const PanelCard: React.FC<PanelCardProps> = (props) => {
13
13
  const {title, className, children} = props
14
14
 
15
15
  return (
16
- <Card className={classNames('panel-card', className)}>
16
+ <Card className={classNames('panel-card', className)} >
17
17
  <Flex className="panel-shell" direction="column">
18
18
  <Text className="panel-title">{title}</Text>
19
19
  <>{children}</>