@forge-kit/plugin-source-map-prase 0.0.2 → 0.0.4

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 +17 -19
  2. package/dist/node.js +2552 -0
  3. package/package.json +5 -3
  4. package/src/App.less +35 -26
  5. package/src/App.tsx +70 -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 +11 -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.4",
14
15
  "type": "module",
15
16
  "scripts": {
16
17
  "dev": "vite",
@@ -22,9 +23,10 @@
22
23
  "@dnd-kit/core": "^6.3.1",
23
24
  "@dnd-kit/sortable": "^10.0.0",
24
25
  "@dnd-kit/utilities": "^3.2.2",
26
+ "@forge-kit/bridge-client": "workspace:*",
25
27
  "@forge-kit/component": "workspace:*",
26
28
  "@forge-kit/icons": "workspace:*",
27
- "@forge-kit/types": "workspace:*",
29
+ "@forge-kit/helper": "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 {applyNodeMethod, canIUse} from "@forge-kit/bridge-client";
8
+ import type {
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,50 @@ 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 mapFilePaths = chainState.chainMapSlots
96
+ .map((slot) => slot.mapFilePath)
97
+ .filter((path): path is string => Boolean(path))
98
+
99
+ if (!canIUse('applyNodeMethod')) return setOutputMessage("Node bridge 不可用,无法执行解析。")
100
+
101
+ if (mapFilePaths.length === 0) return setOutputMessage("未提供 map 文件路径,无法在 Node 侧解析。")
102
+
103
+ const payload = {
104
+ mapFilePaths,
105
+ entryFileName: validated.entry.fileName,
106
+ entryLine: String(validated.entry.lineNumber),
107
+ entryColumn: String(validated.entry.columnNumber),
108
+ contextLineRadius: String(validated.contextLineRadius),
109
+ maxSnippetPayloadBytes: DEFAULT_MAX_SNIPPET_PAYLOAD_BYTES,
110
+ }
111
+
112
+ try {
113
+ const nextOutput = await applyNodeMethod<ResolveTraceMetaOutput>('resolveTrace', JSON.stringify(payload))
114
+ setOutputState((prev) => ({
115
+ ...prev,
116
+ traceData: nextOutput.traceData || [],
117
+ resolvedSourceMeta: nextOutput.resolvedSourceMeta || null,
118
+ traceCode: nextOutput.message || prev.traceCode,
119
+ traceHighlightLines: [],
120
+ }))
121
+ if (nextOutput?.resolvedSourceMeta) {
122
+ try {
123
+ const snippet = await applyNodeMethod<ResolveTraceSnippetOutput>('getSourceSnippet', JSON.stringify(payload))
124
+ setOutputState((prev) => ({...prev, ...snippet}))
125
+ } catch (error) {
126
+ const message = error instanceof Error ? error.message : String(error)
127
+ setOutputState((prev) => SourceMapUtils.patchOutputMessage(prev, `源码片段加载失败: ${message}`))
128
+ }
129
+ }
130
+ } catch (error) {
131
+ const message = error instanceof Error ? error.message : String(error)
132
+ setOutputMessage(`Node resolve failed: ${message}`)
133
+ }
88
134
  }
89
135
 
90
136
  return (
@@ -93,6 +139,7 @@ export const App: React.FC<AppProps> = (props) => {
93
139
  <Flex className="forge-kit-plugin-source-map-prase-content" direction="column" gap="3">
94
140
  <Flex className="top-cards" gap="3" align="stretch">
95
141
  <TraceChain
142
+ className="top-trace-card"
96
143
  slots={chainState.chainMapSlots.map((slot) => ({
97
144
  id: slot.id,
98
145
  mapFileName: slot.mapFileName,
@@ -101,16 +148,16 @@ export const App: React.FC<AppProps> = (props) => {
101
148
  data={traceChainDisplayData}
102
149
  onReorder={handleReorderChainSlots}
103
150
  onDelete={handleRemoveChainSlot}
104
- className="trace-chain-root"
105
151
  />
106
152
 
107
153
  <MapInputPanel
154
+ className="top-input-card"
108
155
  chainDepth={chainState.chainMapSlots.length}
109
156
  entryFileName={chainState.entryFileName}
110
157
  inputConfig={inputState}
111
158
  onInputConfigChange={setInputState}
112
159
  onResolve={handleResolveTrace}
113
- onAppendMapFile={handleAppendChainFile}
160
+ onAppendMapFilePath={handleAppendMapFilePath}
114
161
  />
115
162
  </Flex>
116
163
 
@@ -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,20 @@
1
1
  import * as React from "react";
2
2
  import {Box, Button, Flex, Text, TextField} from "@forge-kit/component";
3
+ import {canIUse, openFileDialog} from "@forge-kit/bridge-client";
3
4
  import {PanelCard} from "@/components/panel-card";
5
+ import type {MapInputConfig} from "@/types";
4
6
  import {noop} from "@/utils";
7
+ import classNames from "classnames";
5
8
  import "./index.less";
6
9
 
7
- interface MapInputConfig {
8
- entryLine: string
9
- entryColumn: string
10
- contextLineRadius: string
11
- }
12
-
13
10
  interface MapInputPanelProps {
11
+ className?: string
14
12
  chainDepth: number
15
13
  entryFileName: string
16
14
  inputConfig: MapInputConfig
17
15
  onInputConfigChange: (next: MapInputConfig) => void
18
16
  onResolve: () => void
19
- onAppendMapFile: (file: File) => void
17
+ onAppendMapFilePath: (filePath: string) => void
20
18
  }
21
19
 
22
20
  interface MapInputFieldProps {
@@ -45,25 +43,26 @@ const MapInputField: React.FC<MapInputFieldProps> = (props) => {
45
43
 
46
44
  export const MapInputPanel: React.FC<MapInputPanelProps> = (props) => {
47
45
  const {
46
+ className,
48
47
  chainDepth,
49
48
  entryFileName,
50
49
  inputConfig,
51
50
  onInputConfigChange,
52
51
  onResolve,
53
- onAppendMapFile,
52
+ onAppendMapFilePath,
54
53
  } = props
55
54
 
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()
55
+ const openFilePicker = async () => {
56
+ if (!canIUse('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}</>