fluidcad 0.0.18 → 0.0.19

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.
@@ -4,6 +4,7 @@ import { SelectSceneObject } from "./features/select.js";
4
4
  import { Sketch } from "./features/2d/sketch.js";
5
5
  import { Extrudable } from "./helpers/types.js";
6
6
  export declare function captureSourceLocation(): SourceLocation | null;
7
+ export declare function extractSourceLocation(stack: string): SourceLocation | null;
7
8
  export type SceneParserContext = {
8
9
  addSceneObject(obj: SceneObject): void;
9
10
  addSceneObjects(objs: SceneObject[]): void;
package/lib/dist/index.js CHANGED
@@ -1,30 +1,40 @@
1
1
  import { loadOC } from "./load.js";
2
2
  import { createManager, getCurrentScene } from "./scene-manager.js";
3
+ import { parse as parseStackTrace } from "stacktrace-parser";
3
4
  export function captureSourceLocation() {
4
5
  const stack = new Error().stack;
5
6
  if (!stack) {
6
7
  return null;
7
8
  }
8
- const lines = stack.split('\n');
9
- for (const frame of lines) {
10
- // Match the Vite live-render virtual prefix first so the path regex
11
- // does not accidentally match `r:/…` inside the word `render:`.
12
- const virtualMatch = frame.match(/virtual:live-render:((?:[A-Za-z]:)?\/[^\s]+?\.fluid\.js):(\d+):(\d+)/);
13
- if (virtualMatch) {
14
- return {
15
- filePath: virtualMatch[1],
16
- line: parseInt(virtualMatch[2], 10),
17
- column: parseInt(virtualMatch[3], 10),
18
- };
9
+ return extractSourceLocation(stack);
10
+ }
11
+ export function extractSourceLocation(stack) {
12
+ const frames = parseStackTrace(stack);
13
+ for (const frame of frames) {
14
+ if (!frame.file || frame.lineNumber == null) {
15
+ continue;
16
+ }
17
+ let filePath = frame.file;
18
+ const virtualPrefix = 'virtual:live-render:';
19
+ const virtualIdx = filePath.lastIndexOf(virtualPrefix);
20
+ if (virtualIdx !== -1) {
21
+ filePath = filePath.slice(virtualIdx + virtualPrefix.length);
19
22
  }
20
- const realMatch = frame.match(/((?:[A-Za-z]:)?\/[^\s]+?\.fluid\.js):(\d+):(\d+)/);
21
- if (realMatch) {
22
- return {
23
- filePath: realMatch[1],
24
- line: parseInt(realMatch[2], 10),
25
- column: parseInt(realMatch[3], 10),
26
- };
23
+ if (filePath.startsWith('file:///')) {
24
+ filePath = filePath.slice('file:///'.length);
25
+ if (!/^[A-Za-z]:/.test(filePath)) {
26
+ filePath = '/' + filePath;
27
+ }
27
28
  }
29
+ if (!filePath.endsWith('.fluid.js')) {
30
+ continue;
31
+ }
32
+ filePath = filePath.replace(/\\/g, '/');
33
+ return {
34
+ filePath,
35
+ line: frame.lineNumber,
36
+ column: frame.column ?? 0,
37
+ };
28
38
  }
29
39
  return null;
30
40
  }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,74 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { extractSourceLocation } from "../index.js";
3
+ describe("extractSourceLocation", () => {
4
+ it("parses Linux virtual:live-render frame", () => {
5
+ const stack = `Error
6
+ at breakpoint (file:///home/user/node_modules/fluidcad/lib/dist/core/breakpoint.js:4:11)
7
+ at eval (virtual:live-render:/home/user/project/test.fluid.js:10:5)`;
8
+ const loc = extractSourceLocation(stack);
9
+ expect(loc).toEqual({
10
+ filePath: "/home/user/project/test.fluid.js",
11
+ line: 10,
12
+ column: 5,
13
+ });
14
+ });
15
+ it("parses Linux real file path", () => {
16
+ const stack = `Error
17
+ at Object.<anonymous> (/home/user/project/test.fluid.js:10:5)`;
18
+ const loc = extractSourceLocation(stack);
19
+ expect(loc).toEqual({
20
+ filePath: "/home/user/project/test.fluid.js",
21
+ line: 10,
22
+ column: 5,
23
+ });
24
+ });
25
+ it("parses Windows virtual:live-render frame with backslashes", () => {
26
+ const stack = `Error
27
+ at breakpoint (file:///C:/Users/marwan/proj/test5/node_modules/fluidcad/lib/dist/core/breakpoint.js:4:11)
28
+ at eval (C:\\Users\\marwan\\AppData\\Local\\Programs\\Microsoft VS Code\\virtual:live-render:C:\\Users\\marwan\\proj\\test5\\test.fluid.js:8:11)`;
29
+ const loc = extractSourceLocation(stack);
30
+ expect(loc).toEqual({
31
+ filePath: "C:/Users/marwan/proj/test5/test.fluid.js",
32
+ line: 8,
33
+ column: 11,
34
+ });
35
+ });
36
+ it("parses Windows file:/// URL with forward slashes", () => {
37
+ const stack = `Error
38
+ at Object.<anonymous> (file:///C:/Users/marwan/proj/test.fluid.js:4:11)`;
39
+ const loc = extractSourceLocation(stack);
40
+ expect(loc).toEqual({
41
+ filePath: "C:/Users/marwan/proj/test.fluid.js",
42
+ line: 4,
43
+ column: 11,
44
+ });
45
+ });
46
+ it("skips non-.fluid.js files", () => {
47
+ const stack = `Error
48
+ at breakpoint (/home/user/node_modules/fluidcad/lib/dist/core/breakpoint.js:4:11)`;
49
+ const loc = extractSourceLocation(stack);
50
+ expect(loc).toBeNull();
51
+ });
52
+ it("skips frames with no file", () => {
53
+ const stack = `Error
54
+ at Array.forEach (<anonymous>)`;
55
+ const loc = extractSourceLocation(stack);
56
+ expect(loc).toBeNull();
57
+ });
58
+ it("returns null for empty stack", () => {
59
+ const loc = extractSourceLocation("");
60
+ expect(loc).toBeNull();
61
+ });
62
+ it("skips breakpoint.js and finds the .fluid.js caller", () => {
63
+ const stack = `Error
64
+ at captureSourceLocation (/home/user/node_modules/fluidcad/lib/dist/index.js:9:15)
65
+ at breakpoint (/home/user/node_modules/fluidcad/lib/dist/core/breakpoint.js:4:11)
66
+ at Object.<anonymous> (/home/user/project/model.fluid.js:3:1)`;
67
+ const loc = extractSourceLocation(stack);
68
+ expect(loc).toEqual({
69
+ filePath: "/home/user/project/model.fluid.js",
70
+ line: 3,
71
+ column: 1,
72
+ });
73
+ });
74
+ });