@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
@@ -0,0 +1,104 @@
1
+ export const DEFAULT_CONTEXT_LINE_RADIUS = 3
2
+
3
+ export interface TraceLocation {
4
+ fileName: string
5
+ lineNumber: number
6
+ columnNumber: number
7
+ }
8
+
9
+ export interface ResolvedSourceMeta {
10
+ sourceFile: string
11
+ lineNumber: number
12
+ columnNumber: number
13
+ }
14
+
15
+ export interface TraceSnippetState {
16
+ traceCode: string
17
+ resolvedSourceMeta: ResolvedSourceMeta | null
18
+ traceHighlightLines: number[]
19
+ }
20
+
21
+ export interface TraceMetaOutputState<TTraceHop = unknown> {
22
+ traceData: TTraceHop[]
23
+ resolvedSourceMeta: ResolvedSourceMeta | null
24
+ canFetchSourceSnippet: boolean
25
+ message: string
26
+ }
27
+
28
+ export interface ResolveInputConfig {
29
+ entryFileName: string
30
+ entryLine: string
31
+ entryColumn: string
32
+ contextLineRadius: string
33
+ mapCount: number
34
+ }
35
+
36
+ export type ResolveInputValidation =
37
+ | { ok: true; entry: TraceLocation; contextLineRadius: number }
38
+ | { ok: false; message: string }
39
+
40
+ export interface TraceOutputState<TTraceHop = unknown> {
41
+ traceData: TTraceHop[]
42
+ traceCode: string
43
+ resolvedSourceMeta: ResolvedSourceMeta | null
44
+ traceHighlightLines: number[]
45
+ }
46
+
47
+ export const createInitialOutputState = <TTraceHop = unknown>(): TraceOutputState<TTraceHop> => {
48
+ return {
49
+ traceData: [],
50
+ traceCode: "请先上传一个或多个 .map 文件。",
51
+ resolvedSourceMeta: null,
52
+ traceHighlightLines: [],
53
+ }
54
+ }
55
+
56
+ export const patchOutputMessage = <TTraceHop = unknown>(
57
+ prev: TraceOutputState<TTraceHop>,
58
+ message: string,
59
+ ): TraceOutputState<TTraceHop> => {
60
+ return {
61
+ ...prev,
62
+ traceCode: message,
63
+ traceHighlightLines: [],
64
+ }
65
+ }
66
+
67
+ export const validateResolveInput = (config: ResolveInputConfig): ResolveInputValidation => {
68
+ const parsedLine = Number(config.entryLine)
69
+ const parsedColumn = Number(config.entryColumn)
70
+ const parsedContextLineRadius = Number(config.contextLineRadius)
71
+ const normalizedFileName = config.entryFileName.replace(/\\/g, "/").trim()
72
+
73
+ if (!normalizedFileName) {
74
+ return {ok: false, message: "请输入入口文件名,例如 index.js"}
75
+ }
76
+ if (!config.entryLine.trim()) {
77
+ return {ok: false, message: "请输入入口行号(1-based)。"}
78
+ }
79
+ if (!config.entryColumn.trim()) {
80
+ return {ok: false, message: "请输入入口列号(0-based)。"}
81
+ }
82
+ if (!Number.isInteger(parsedLine) || parsedLine < 1) {
83
+ return {ok: false, message: "行号必须是大于等于 1 的整数。"}
84
+ }
85
+ if (!Number.isInteger(parsedColumn) || parsedColumn < 0) {
86
+ return {ok: false, message: "列号必须是大于等于 0 的整数。"}
87
+ }
88
+ if (!Number.isInteger(parsedContextLineRadius) || parsedContextLineRadius < 0) {
89
+ return {ok: false, message: "上下文行数必须是大于等于 0 的整数。"}
90
+ }
91
+ if (config.mapCount === 0) {
92
+ return {ok: false, message: "未加载可用 map 文件,请先上传。"}
93
+ }
94
+
95
+ return {
96
+ ok: true,
97
+ entry: {
98
+ fileName: normalizedFileName,
99
+ lineNumber: parsedLine,
100
+ columnNumber: parsedColumn,
101
+ },
102
+ contextLineRadius: parsedContextLineRadius,
103
+ }
104
+ }
@@ -0,0 +1,29 @@
1
+ import type {ResolvedSourceMeta, TraceLocation} from "@/shared/trace-common";
2
+
3
+ export interface ResolveTraceInput {
4
+ entryFileName: string
5
+ entryLine: string
6
+ entryColumn: string
7
+ contextLineRadius?: string
8
+ mapFilePaths: string[]
9
+ maxSnippetPayloadBytes?: string
10
+ }
11
+
12
+ export interface TraceHopPayload {
13
+ input: TraceLocation
14
+ output?: TraceLocation
15
+ mapFileName?: string
16
+ }
17
+
18
+ export interface ResolveTraceMetaOutput {
19
+ traceData: TraceHopPayload[]
20
+ resolvedSourceMeta: ResolvedSourceMeta | null
21
+ canFetchSourceSnippet: boolean
22
+ message: string
23
+ }
24
+
25
+ export interface ResolveTraceSnippetOutput {
26
+ traceCode: string
27
+ resolvedSourceMeta: ResolvedSourceMeta | null
28
+ traceHighlightLines: number[]
29
+ }
package/src/types.ts ADDED
@@ -0,0 +1,19 @@
1
+ export type {
2
+ ResolveTraceInput,
3
+ ResolveTraceMetaOutput,
4
+ ResolveTraceSnippetOutput,
5
+ } from "@/shared/trace-contract";
6
+
7
+ export interface MapInputConfig {
8
+ entryLine: string
9
+ entryColumn: string
10
+ contextLineRadius: string
11
+ }
12
+
13
+ export interface ForgeKitBridge {
14
+ applyNodeMethod?: (method: string, ...args: string[]) => Promise<unknown>
15
+ openFileDialog?: (options: {
16
+ multiple?: boolean
17
+ filters?: {name: string; extensions: string[]}[]
18
+ }) => Promise<string | string[] | null>
19
+ }
@@ -0,0 +1,12 @@
1
+ export {SourceMapUtils} from "./state"
2
+
3
+ export type {
4
+ TraceOutputState,
5
+ SourceMapChainState,
6
+ TraceDisplayHop,
7
+ ResolveInputConfig,
8
+ ChainMapSlot,
9
+ TraceLocation,
10
+ TraceHop,
11
+ ResolvedSourceMeta,
12
+ } from "./state"
@@ -0,0 +1,81 @@
1
+ import {
2
+ DEFAULT_CONTEXT_LINE_RADIUS,
3
+ createInitialOutputState,
4
+ patchOutputMessage,
5
+ validateResolveInput,
6
+ } from "@/shared/trace-common";
7
+ import type {
8
+ ResolveInputConfig,
9
+ ResolveInputValidation,
10
+ TraceLocation,
11
+ TraceOutputState as SharedTraceOutputState,
12
+ } from "@/shared/trace-common";
13
+ export type {ResolveInputConfig, ResolvedSourceMeta, TraceLocation} from "@/shared/trace-common";
14
+
15
+ export interface TraceHop {
16
+ input: TraceLocation
17
+ output?: TraceLocation
18
+ mapFileName?: string
19
+ }
20
+
21
+ export type TraceOutputState = SharedTraceOutputState<TraceHop>
22
+
23
+ export interface SourceMapChainState {
24
+ chainMapSlots: ChainMapSlot[]
25
+ entryFileName: string
26
+ }
27
+
28
+ export interface ChainMapSlot {
29
+ id: string
30
+ mapFileName: string
31
+ mapFilePath?: string
32
+ error?: string
33
+ }
34
+
35
+ export interface TraceDisplayHop {
36
+ input: TraceLocation
37
+ output?: TraceLocation
38
+ mapFileName?: string
39
+ }
40
+
41
+ export class SourceMapUtils {
42
+ static readonly DEFAULT_CONTEXT_LINE_RADIUS = DEFAULT_CONTEXT_LINE_RADIUS
43
+
44
+ static createInitialOutputState(): TraceOutputState {
45
+ return createInitialOutputState<TraceHop>()
46
+ }
47
+
48
+ static patchOutputMessage(prev: TraceOutputState, message: string): TraceOutputState {
49
+ return patchOutputMessage(prev, message)
50
+ }
51
+
52
+ static removeChainSlot(slots: ChainMapSlot[], slotId: string): ChainMapSlot[] {
53
+ return slots.filter((item) => item.id !== slotId)
54
+ }
55
+
56
+ static reorderChainSlots(slots: ChainMapSlot[], nextIds: string[]): ChainMapSlot[] {
57
+ const idOrder = new Map(nextIds.map((id, index) => [id, index]))
58
+ return [...slots].sort((a, b) => {
59
+ const aIndex = idOrder.get(a.id) ?? Number.MAX_SAFE_INTEGER
60
+ const bIndex = idOrder.get(b.id) ?? Number.MAX_SAFE_INTEGER
61
+ return aIndex - bIndex
62
+ })
63
+ }
64
+
65
+ static mapTraceForDisplay(traceData: TraceHop[]): TraceDisplayHop[] {
66
+ return traceData.map((item) => {
67
+ let output: TraceLocation | undefined
68
+ if (item.output) output = {...item.output}
69
+ return {
70
+ input: {...item.input},
71
+ output,
72
+ mapFileName: item.mapFileName,
73
+ }
74
+ })
75
+ }
76
+
77
+ static validateResolveInput(config: ResolveInputConfig): ResolveInputValidation {
78
+ return validateResolveInput(config)
79
+ }
80
+
81
+ }
@@ -1,59 +0,0 @@
1
- import {normalizePath, stripMapExt} from "../base/path";
2
- import {registerSourceMap} from "../base/registry";
3
- import type {ChainMapSlot, SourceMapRegistry} from "../base/types";
4
-
5
- export const createChainSlotFromFile = async (file: File): Promise<ChainMapSlot> => {
6
- const slotId = `${file.name}-${Math.random().toString(36).slice(2, 8)}`
7
-
8
- try {
9
- const fileText = await file.text()
10
- const rawSourceMap = JSON.parse(fileText)
11
-
12
- if (!rawSourceMap || typeof rawSourceMap !== "object" || typeof rawSourceMap.mappings !== "string") {
13
- throw new Error("invalid source-map json")
14
- }
15
-
16
- return {
17
- id: slotId,
18
- mapFileName: file.name,
19
- sourceMapRecord: {rawSourceMap, mapFileName: file.name},
20
- }
21
- } catch (error) {
22
- const message = error instanceof Error ? error.message : "unknown error"
23
- return {
24
- id: slotId,
25
- mapFileName: file.name,
26
- error: `${file.name}: ${message}`,
27
- }
28
- }
29
- }
30
-
31
- export const buildRegistryFromChainSlots = (slots: ChainMapSlot[]) => {
32
- let registry: SourceMapRegistry = {}
33
- const orderedMapFileNames: string[] = []
34
-
35
- slots.forEach((slot) => {
36
- const {sourceMapRecord: record} = slot
37
- if (!record) return
38
-
39
- orderedMapFileNames.push(record.mapFileName)
40
- registry = registerSourceMap(registry, record.mapFileName, record)
41
- registry = registerSourceMap(registry, stripMapExt(record.mapFileName), record)
42
-
43
- if (record.rawSourceMap.file) {
44
- registry = registerSourceMap(registry, record.rawSourceMap.file, record)
45
- }
46
- })
47
-
48
- return {registry, orderedMapFileNames}
49
- }
50
-
51
- export const getAutoEntryFileNameFromSlots = (slots: ChainMapSlot[]) => {
52
- const firstValidSlot = slots.find((slot) => slot.sourceMapRecord)
53
- if (!firstValidSlot?.sourceMapRecord) return ""
54
-
55
- const mappedFile = firstValidSlot.sourceMapRecord.rawSourceMap.file
56
- if (mappedFile) return normalizePath(mappedFile)
57
-
58
- return stripMapExt(firstValidSlot.sourceMapRecord.mapFileName)
59
- }
@@ -1,165 +0,0 @@
1
- import {SourceMapConsumer, type RawSourceMap} from "source-map-js";
2
- import {MAX_TRACE_DEPTH} from "../base/constants";
3
- import {getSourceMapByFileName, getSourceMapByMapFileName} from "../base/registry";
4
- import type {ResolveTraceOptions, SourceMapRecord, SourceMapRegistry, TraceHop, TraceLocation} from "../base/types";
5
-
6
- interface ResolvedTraceHop extends TraceHop {
7
- output: TraceLocation
8
- }
9
-
10
- const toLocationCursor = (location: TraceLocation) => {
11
- return `${location.fileName}:${location.lineNumber}:${location.columnNumber}`
12
- }
13
-
14
- const isSameLocation = (left: TraceLocation, right: TraceLocation) => {
15
- return (
16
- left.fileName === right.fileName
17
- && left.lineNumber === right.lineNumber
18
- && left.columnNumber === right.columnNumber
19
- )
20
- }
21
-
22
- const resolveOriginalLocation = (
23
- rawSourceMap: RawSourceMap,
24
- position: { line: number; column: number },
25
- ): TraceLocation | null => {
26
- const consumer = new SourceMapConsumer(rawSourceMap)
27
-
28
- const attempts = [
29
- {line: position.line, column: position.column, bias: SourceMapConsumer.GREATEST_LOWER_BOUND},
30
- {line: position.line, column: 0, bias: SourceMapConsumer.GREATEST_LOWER_BOUND},
31
- {line: position.line, column: position.column, bias: SourceMapConsumer.LEAST_UPPER_BOUND},
32
- {line: position.line, column: 0, bias: SourceMapConsumer.LEAST_UPPER_BOUND},
33
- ]
34
-
35
- for (const attempt of attempts) {
36
- const found = consumer.originalPositionFor(attempt)
37
- if (found.source && found.line != null && found.column != null) {
38
- return {
39
- fileName: found.source,
40
- lineNumber: found.line,
41
- columnNumber: found.column,
42
- }
43
- }
44
- }
45
-
46
- return null
47
- }
48
-
49
- const resolveHopFromRecord = (sourceMapRecord: SourceMapRecord, input: TraceLocation): ResolvedTraceHop | null => {
50
- try {
51
- const output = resolveOriginalLocation(sourceMapRecord.rawSourceMap, {
52
- line: input.lineNumber,
53
- column: input.columnNumber,
54
- })
55
- if (!output) return null
56
-
57
- return {
58
- input,
59
- output,
60
- mapFileName: sourceMapRecord.mapFileName,
61
- }
62
- } catch {
63
- return null
64
- }
65
- }
66
-
67
- const appendUnresolvedHop = (traceHops: TraceHop[], input: TraceLocation, mapFileName?: string) => {
68
- traceHops.push({input, mapFileName})
69
- }
70
-
71
- const resolveTraceWithOrderedMaps = (
72
- entry: TraceLocation,
73
- sourceMaps: SourceMapRegistry,
74
- orderedMapFileNames: string[],
75
- maxDepth: number,
76
- ) => {
77
- const traceHops: TraceHop[] = []
78
- const visited = new Set<string>()
79
- let current = entry
80
-
81
- for (let depth = 0; depth < orderedMapFileNames.length && depth < maxDepth; depth++) {
82
- const mapFileName = orderedMapFileNames[depth]
83
- const cursor = toLocationCursor(current)
84
-
85
- if (visited.has(cursor)) {
86
- appendUnresolvedHop(traceHops, current, mapFileName)
87
- continue
88
- }
89
- visited.add(cursor)
90
-
91
- const sourceMapRecord = getSourceMapByMapFileName(sourceMaps, mapFileName)
92
- if (!sourceMapRecord) {
93
- appendUnresolvedHop(traceHops, current, mapFileName)
94
- continue
95
- }
96
-
97
- const hop = resolveHopFromRecord(sourceMapRecord, current)
98
- if (!hop) {
99
- appendUnresolvedHop(traceHops, current, sourceMapRecord.mapFileName)
100
- continue
101
- }
102
-
103
- traceHops.push(hop)
104
- current = hop.output
105
- }
106
-
107
- return traceHops
108
- }
109
-
110
- const resolveTraceByFileLookup = (
111
- entry: TraceLocation,
112
- sourceMaps: SourceMapRegistry,
113
- maxDepth: number,
114
- ) => {
115
- const traceHops: TraceHop[] = []
116
- const visited = new Set<string>()
117
- let current = entry
118
-
119
- for (let depth = 0; depth < maxDepth; depth++) {
120
- const cursor = toLocationCursor(current)
121
-
122
- if (visited.has(cursor)) {
123
- appendUnresolvedHop(traceHops, current)
124
- break
125
- }
126
- visited.add(cursor)
127
-
128
- const sourceMapRecord = getSourceMapByFileName(sourceMaps, current.fileName)
129
- if (!sourceMapRecord) {
130
- if (traceHops.length === 0) {
131
- appendUnresolvedHop(traceHops, current)
132
- }
133
- break
134
- }
135
-
136
- const hop = resolveHopFromRecord(sourceMapRecord, current)
137
- if (!hop) {
138
- appendUnresolvedHop(traceHops, current, sourceMapRecord.mapFileName)
139
- break
140
- }
141
-
142
- traceHops.push(hop)
143
- if (isSameLocation(hop.output, current)) {
144
- break
145
- }
146
- current = hop.output
147
- }
148
-
149
- return traceHops
150
- }
151
-
152
- export const resolveTraceBySourceMaps = async (
153
- entry: TraceLocation,
154
- sourceMaps: SourceMapRegistry,
155
- orderedMapFileNames: string[] = [],
156
- options: ResolveTraceOptions = {},
157
- ): Promise<TraceHop[]> => {
158
- const maxDepth = options.maxDepth ?? MAX_TRACE_DEPTH
159
-
160
- if (orderedMapFileNames.length > 0) {
161
- return resolveTraceWithOrderedMaps(entry, sourceMaps, orderedMapFileNames, maxDepth)
162
- }
163
-
164
- return resolveTraceByFileLookup(entry, sourceMaps, maxDepth)
165
- }
@@ -1,20 +0,0 @@
1
- import {DEFAULT_CONTEXT_LINE_RADIUS} from "../base/constants";
2
- import type {TraceLocation} from "../base/types";
3
-
4
- export const toDisplayLocation = (location: TraceLocation): TraceLocation => ({...location})
5
-
6
- export const createCodeExcerpt = (fullCode: string, targetLine: number, radius = DEFAULT_CONTEXT_LINE_RADIUS) => {
7
- const lines = fullCode.split(/\r?\n/)
8
- if (lines.length === 0) {
9
- return {code: fullCode, highlightLine: targetLine}
10
- }
11
-
12
- const safeTarget = Math.min(Math.max(targetLine, 1), lines.length)
13
- const start = Math.max(1, safeTarget - radius)
14
- const end = Math.min(lines.length, safeTarget + radius)
15
-
16
- return {
17
- code: lines.slice(start - 1, end).join("\n"),
18
- highlightLine: safeTarget - start + 1,
19
- }
20
- }