@eventra_dev/eventra-cli 0.1.25 → 0.1.26

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.
@@ -12,20 +12,27 @@ class TSService {
12
12
  files = new Map();
13
13
  fileNames = new Set();
14
14
  service;
15
- compilerOptions = {};
16
- baseUrl = "";
17
- paths = {};
15
+ compilerOptions;
16
+ baseUrl;
17
+ paths;
18
+ usage = new Map();
19
+ tick = 0;
20
+ MAX_FILES = 2000;
18
21
  constructor(root) {
19
22
  this.root = root;
20
23
  const configPath = typescript_1.default.findConfigFile(root, typescript_1.default.sys.fileExists, "tsconfig.json");
21
24
  let compilerOptions = {
22
25
  allowJs: true,
23
- jsx: typescript_1.default.JsxEmit.Preserve,
26
+ jsx: typescript_1.default.JsxEmit.React,
27
+ jsxImportSource: "react",
24
28
  moduleResolution: typescript_1.default.ModuleResolutionKind.Node16,
25
29
  target: typescript_1.default.ScriptTarget.ESNext,
26
30
  esModuleInterop: true,
27
31
  allowSyntheticDefaultImports: true,
32
+ skipLibCheck: true,
28
33
  };
34
+ let baseUrl = root;
35
+ let paths = {};
29
36
  if (configPath) {
30
37
  const configFile = typescript_1.default.readConfigFile(configPath, typescript_1.default.sys.readFile);
31
38
  const parsed = typescript_1.default.parseJsonConfigFileContent(configFile.config, typescript_1.default.sys, root);
@@ -33,46 +40,104 @@ class TSService {
33
40
  ...compilerOptions,
34
41
  ...parsed.options,
35
42
  };
36
- this.paths = parsed.options.paths || {};
37
- this.baseUrl = parsed.options.baseUrl
43
+ paths = parsed.options.paths || {};
44
+ baseUrl = parsed.options.baseUrl
38
45
  ? path_1.default.resolve(root, parsed.options.baseUrl)
39
46
  : root;
40
47
  }
41
- else {
42
- this.baseUrl = root;
43
- }
44
48
  this.compilerOptions = compilerOptions;
49
+ this.baseUrl = baseUrl;
50
+ this.paths = paths;
45
51
  const host = {
46
- getScriptFileNames: () => {
47
- return [...new Set(this.fileNames)].sort((a, b) => a.localeCompare(b));
52
+ getScriptFileNames: () => Array.from(this.fileNames),
53
+ getScriptVersion: (file) => {
54
+ file = this.normalize(file);
55
+ return this.files.get(file)?.version.toString() ?? "0";
48
56
  },
49
- getScriptVersion: (file) => this.files.get(file)?.version.toString() ?? "0",
50
57
  getScriptSnapshot: (file) => {
58
+ file = this.normalize(file);
51
59
  const f = this.files.get(file);
52
60
  if (f) {
61
+ this.touch(file);
53
62
  return typescript_1.default.ScriptSnapshot.fromString(f.content);
54
63
  }
55
- if (fs_1.default.existsSync(file)) {
56
- return typescript_1.default.ScriptSnapshot.fromString(fs_1.default.readFileSync(file, "utf-8"));
64
+ try {
65
+ if (fs_1.default.existsSync(file)) {
66
+ const content = fs_1.default.readFileSync(file, "utf-8");
67
+ this.files.set(file, {
68
+ content,
69
+ version: 1,
70
+ });
71
+ this.fileNames.add(file);
72
+ this.touch(file);
73
+ this.evictIfNeeded();
74
+ return typescript_1.default.ScriptSnapshot.fromString(content);
75
+ }
57
76
  }
58
- return undefined;
77
+ catch { }
78
+ // fallback
79
+ return typescript_1.default.ScriptSnapshot.fromString("");
59
80
  },
60
81
  getCurrentDirectory: () => this.root,
61
82
  getCompilationSettings: () => this.compilerOptions,
62
83
  getDefaultLibFileName: (opts) => typescript_1.default.getDefaultLibFilePath(opts),
63
- readFile: typescript_1.default.sys.readFile,
64
84
  fileExists: typescript_1.default.sys.fileExists,
85
+ readFile: typescript_1.default.sys.readFile,
65
86
  readDirectory: typescript_1.default.sys.readDirectory,
66
87
  };
67
88
  this.service = typescript_1.default.createLanguageService(host);
68
89
  }
90
+ // INTERNAL
91
+ normalize(file) {
92
+ return path_1.default.resolve(file).replace(/\\/g, "/");
93
+ }
94
+ touch(file) {
95
+ this.tick++;
96
+ this.usage.set(file, this.tick);
97
+ }
98
+ evictIfNeeded() {
99
+ if (this.fileNames.size <= this.MAX_FILES)
100
+ return;
101
+ let oldestFile = null;
102
+ let oldestTick = Infinity;
103
+ for (const f of this.fileNames) {
104
+ const t = this.usage.get(f) ?? 0;
105
+ if (t < oldestTick) {
106
+ oldestTick = t;
107
+ oldestFile = f;
108
+ }
109
+ }
110
+ if (oldestFile) {
111
+ this.files.delete(oldestFile);
112
+ this.fileNames.delete(oldestFile);
113
+ this.usage.delete(oldestFile);
114
+ }
115
+ }
116
+ ensureProgram() {
117
+ try {
118
+ this.service.getProgram();
119
+ }
120
+ catch { }
121
+ }
122
+ // PUBLIC API
69
123
  updateFile(file, content) {
124
+ file = this.normalize(file);
70
125
  const prev = this.files.get(file);
71
126
  this.files.set(file, {
72
127
  content,
73
128
  version: prev ? prev.version + 1 : 1,
74
129
  });
75
130
  this.fileNames.add(file);
131
+ this.touch(file);
132
+ this.evictIfNeeded();
133
+ this.ensureProgram();
134
+ }
135
+ removeFile(file) {
136
+ file = this.normalize(file);
137
+ this.files.delete(file);
138
+ this.fileNames.delete(file);
139
+ this.usage.delete(file);
140
+ this.ensureProgram();
76
141
  }
77
142
  getProgram() {
78
143
  return this.service.getProgram();
@@ -81,15 +146,13 @@ class TSService {
81
146
  return this.getProgram()?.getTypeChecker();
82
147
  }
83
148
  getSourceFile(file) {
149
+ file = this.normalize(file);
84
150
  return this.getProgram()?.getSourceFile(file);
85
151
  }
86
152
  getFileContent(file) {
153
+ file = this.normalize(file);
87
154
  return this.files.get(file)?.content;
88
155
  }
89
- removeFile(file) {
90
- this.files.delete(file);
91
- this.fileNames.delete(file);
92
- }
93
156
  getModuleResolutionConfig() {
94
157
  return {
95
158
  baseUrl: this.baseUrl,
@@ -11,7 +11,7 @@ function resolveNodeValue(node, checker, paramMap, seen = new Set()) {
11
11
  return { values: [], dynamic: true };
12
12
  }
13
13
  seen.add(node);
14
- // param mapping
14
+ // PARAM MAPPING
15
15
  if (typescript_1.default.isIdentifier(node) && paramMap?.has(node.text)) {
16
16
  return resolveNodeValue(paramMap.get(node.text), checker, paramMap, seen);
17
17
  }
@@ -28,11 +28,14 @@ function resolveNodeValue(node, checker, paramMap, seen = new Set()) {
28
28
  values.push(...res.values);
29
29
  }
30
30
  }
31
- return { values, dynamic: true };
31
+ return {
32
+ values: [...new Set(values)],
33
+ dynamic: true
34
+ };
32
35
  }
33
36
  // TEMPLATE
34
37
  if (typescript_1.default.isTemplateExpression(node)) {
35
- let results = [node.head.text];
38
+ let results = node.head.text ? [node.head.text] : [""];
36
39
  for (const span of node.templateSpans) {
37
40
  const res = resolveNodeValue(span.expression, checker, paramMap, seen);
38
41
  if (res.values.length) {
@@ -59,16 +62,18 @@ function resolveNodeValue(node, checker, paramMap, seen = new Set()) {
59
62
  let symbol = checker.getSymbolAtLocation(node);
60
63
  if (!symbol)
61
64
  return { values: [], dynamic: true };
65
+ // INLINE FIRST
66
+ for (const decl of symbol.getDeclarations() ?? []) {
67
+ if (typescript_1.default.isVariableDeclaration(decl) && decl.initializer) {
68
+ return resolveNodeValue(decl.initializer, checker, paramMap, seen);
69
+ }
70
+ }
62
71
  // unwrap export
63
72
  symbol = (0, exportResolver_1.resolveExportedSymbol)(symbol, checker) ?? symbol;
64
73
  if (symbol.flags & typescript_1.default.SymbolFlags.Alias) {
65
74
  symbol = checker.getAliasedSymbol(symbol);
66
75
  }
67
- // inline variable
68
76
  for (const decl of symbol.getDeclarations() ?? []) {
69
- if (typescript_1.default.isVariableDeclaration(decl) && decl.initializer) {
70
- return resolveNodeValue(decl.initializer, checker, paramMap, seen);
71
- }
72
77
  if (typescript_1.default.isEnumMember(decl) && decl.initializer) {
73
78
  if (typescript_1.default.isStringLiteral(decl.initializer)) {
74
79
  return {
@@ -78,12 +83,12 @@ function resolveNodeValue(node, checker, paramMap, seen = new Set()) {
78
83
  }
79
84
  }
80
85
  }
81
- // UNION TYPES
82
86
  const type = checker.getTypeAtLocation(node);
87
+ // UNION
83
88
  if (type.isUnion()) {
84
89
  const values = [];
85
90
  for (const t of type.types) {
86
- if (t.isStringLiteral?.()) {
91
+ if (t.flags & typescript_1.default.TypeFlags.StringLiteral) {
87
92
  values.push(t.value);
88
93
  }
89
94
  }
@@ -91,13 +96,16 @@ function resolveNodeValue(node, checker, paramMap, seen = new Set()) {
91
96
  return { values, dynamic: false };
92
97
  }
93
98
  }
94
- // TYPE FALLBACK (as const)
95
- if (type.isStringLiteral?.()) {
96
- return { values: [type.value], dynamic: false };
99
+ // STRING LITERAL TYPE
100
+ if (type.flags & typescript_1.default.TypeFlags.StringLiteral) {
101
+ return {
102
+ values: [type.value],
103
+ dynamic: false
104
+ };
97
105
  }
98
106
  return { values: [], dynamic: true };
99
107
  }
100
- // PROPERTY ACCESS (obj.a.b)
108
+ // PROPERTY ACCESS
101
109
  if (typescript_1.default.isPropertyAccessExpression(node)) {
102
110
  const symbol = checker.getSymbolAtLocation(node);
103
111
  if (symbol) {
@@ -116,14 +124,12 @@ function resolveNodeValue(node, checker, paramMap, seen = new Set()) {
116
124
  }
117
125
  }
118
126
  }
119
- // constant enum / readonly
120
127
  const constantValue = checker.getConstantValue(node);
121
128
  if (typeof constantValue === "string") {
122
129
  return { values: [constantValue], dynamic: false };
123
130
  }
124
- // type fallback
125
131
  const type = checker.getTypeAtLocation(node);
126
- if (type.isStringLiteral?.()) {
132
+ if (type.flags & typescript_1.default.TypeFlags.StringLiteral) {
127
133
  return {
128
134
  values: [type.value],
129
135
  dynamic: false,
@@ -149,16 +155,16 @@ function resolveNodeValue(node, checker, paramMap, seen = new Set()) {
149
155
  }
150
156
  }
151
157
  }
152
- return { values: [], dynamic: true };
158
+ // fallback
159
+ return resolveNodeValue(node.name, checker, paramMap, seen);
153
160
  }
154
- // ARRAY ACCESS (arr[0])
161
+ // ARRAY ACCESS
155
162
  if (typescript_1.default.isElementAccessExpression(node)) {
156
163
  const obj = resolveNodeValue(node.expression, checker, paramMap, seen);
157
164
  if (!node.argumentExpression) {
158
165
  return { values: obj.values, dynamic: true };
159
166
  }
160
167
  const index = resolveNodeValue(node.argumentExpression, checker, paramMap, seen);
161
- // if index known
162
168
  if (index.values.length === 1) {
163
169
  const i = Number(index.values[0]);
164
170
  if (!Number.isNaN(i) && obj.values[i] !== undefined) {
@@ -168,7 +174,6 @@ function resolveNodeValue(node, checker, paramMap, seen = new Set()) {
168
174
  };
169
175
  }
170
176
  }
171
- // fallback → dynamic
172
177
  return obj.values.length
173
178
  ? { values: obj.values, dynamic: true }
174
179
  : { values: [], dynamic: true };
@@ -185,7 +190,10 @@ function resolveNodeValue(node, checker, paramMap, seen = new Set()) {
185
190
  values.push(...res.values);
186
191
  }
187
192
  }
188
- return { values, dynamic: true };
193
+ return {
194
+ values: [...new Set(values)],
195
+ dynamic: true
196
+ };
189
197
  }
190
198
  // BINARY ("btn_" + name)
191
199
  if (typescript_1.default.isBinaryExpression(node)) {
@@ -10,7 +10,7 @@ const callResolver_1 = require("./callResolver");
10
10
  const exportResolver_1 = require("./exportResolver");
11
11
  const functionAnalyzer_1 = require("./functionAnalyzer");
12
12
  const boundary_1 = require("./boundary");
13
- const TRACKING_NAMES = new Set(["track", "event", "capture", "send"]);
13
+ const trackingDetector_1 = require("./trackingDetector");
14
14
  function getCallName(expr) {
15
15
  if (typescript_1.default.isIdentifier(expr))
16
16
  return expr.text;
@@ -25,6 +25,16 @@ function getJsxAttrName(name) {
25
25
  return name.name.text;
26
26
  return null;
27
27
  }
28
+ // unwrap
29
+ function unwrap(expr) {
30
+ while (typescript_1.default.isAsExpression(expr) ||
31
+ typescript_1.default.isParenthesizedExpression(expr) ||
32
+ typescript_1.default.isTypeAssertionExpression(expr) ||
33
+ typescript_1.default.isNonNullExpression(expr)) {
34
+ expr = expr.expression;
35
+ }
36
+ return expr;
37
+ }
28
38
  function isValidEvent(v) {
29
39
  return (v &&
30
40
  v.length > 1 &&
@@ -32,70 +42,75 @@ function isValidEvent(v) {
32
42
  !v.includes(" ") &&
33
43
  /^[a-zA-Z0-9._:-]+$/.test(v));
34
44
  }
35
- function isPropUsedInTracking(fn, prop) {
45
+ function isPropUsedInTracking(fn, checker, prop) {
36
46
  let found = false;
37
47
  function visit(node) {
38
48
  if (typescript_1.default.isCallExpression(node)) {
39
- const name = getCallName(node.expression);
40
- if (!TRACKING_NAMES.has(name)) {
49
+ if (!(0, trackingDetector_1.isTrackingCall)(node, checker)) {
41
50
  typescript_1.default.forEachChild(node, visit);
42
51
  return;
43
52
  }
44
53
  for (const arg of node.arguments) {
45
- if ((typescript_1.default.isIdentifier(arg) && arg.text === prop) ||
46
- (typescript_1.default.isPropertyAccessExpression(arg) &&
47
- arg.name.text === prop)) {
54
+ const a = unwrap(arg);
55
+ if ((typescript_1.default.isIdentifier(a) && a.text === prop) ||
56
+ (typescript_1.default.isPropertyAccessExpression(a) &&
57
+ a.name.text === prop)) {
48
58
  found = true;
49
59
  }
50
60
  }
51
61
  }
52
62
  typescript_1.default.forEachChild(node, visit);
53
63
  }
54
- visit(fn);
64
+ if (fn.body) {
65
+ visit(fn.body);
66
+ }
55
67
  return found;
56
68
  }
57
69
  function scanSource(source, checker, config, visited = new Set(), depth = 0, inheritedParamMap) {
58
70
  const events = new Set();
59
71
  const detectedFunctionWrappers = new Set();
60
72
  const detectedComponentWrappers = new Map();
61
- if (depth > 5 || visited.has(source.fileName)) {
73
+ if (visited.has(source.fileName) ||
74
+ depth > 10 ||
75
+ visited.size > 200) {
62
76
  return { events, detectedFunctionWrappers, detectedComponentWrappers };
63
77
  }
64
78
  visited.add(source.fileName);
65
79
  function visit(node) {
66
80
  // CALL EXPRESSIONS
67
81
  if (typescript_1.default.isCallExpression(node)) {
68
- const name = getCallName(node.expression);
82
+ const expr = unwrap(node.expression);
83
+ const name = getCallName(expr);
69
84
  const isKnownWrapper = config.functionWrappers.some(w => w.name === name);
70
- let resolvedFn = (0, callResolver_1.resolveFunctionFromCall)(node.expression, checker);
71
- // fallback: tracker.track(...)
72
- if (!resolvedFn && typescript_1.default.isPropertyAccessExpression(node.expression)) {
73
- const symbol = checker.getSymbolAtLocation(node.expression.name);
74
- if (symbol) {
75
- const decls = symbol.getDeclarations();
76
- if (decls?.length) {
77
- const d = decls[0];
78
- if (typescript_1.default.isMethodDeclaration(d) ||
79
- typescript_1.default.isFunctionDeclaration(d) ||
80
- typescript_1.default.isFunctionExpression(d) ||
81
- typescript_1.default.isArrowFunction(d)) {
82
- resolvedFn = d;
83
- }
84
- }
85
- }
86
- }
85
+ const isTrackingByType = (0, trackingDetector_1.isTrackingCall)(node, checker);
86
+ let resolvedFn = (0, callResolver_1.resolveFunctionFromCall)(expr, checker);
87
87
  const isAutoWrapper = resolvedFn && (0, functionAnalyzer_1.findTrackParamIndex)(resolvedFn) !== null;
88
- const isTracking = isKnownWrapper ||
89
- TRACKING_NAMES.has(name) ||
90
- name.startsWith("$") || // NUXT FIX
88
+ let isTracking = isKnownWrapper ||
89
+ isTrackingByType ||
90
+ name.startsWith("$") ||
91
91
  (isAutoWrapper &&
92
92
  resolvedFn &&
93
93
  !(0, boundary_1.isExternalFile)(resolvedFn.getSourceFile().fileName));
94
+ // fallback
95
+ if (!isTracking) {
96
+ if (typescript_1.default.isPropertyAccessExpression(expr) &&
97
+ expr.name.text === "track") {
98
+ isTracking = true;
99
+ }
100
+ else if (typescript_1.default.isIdentifier(expr) &&
101
+ expr.text === "track") {
102
+ isTracking = true;
103
+ }
104
+ }
94
105
  if (!isTracking) {
95
106
  typescript_1.default.forEachChild(node, visit);
96
107
  return;
97
108
  }
98
- if (!isKnownWrapper && isAutoWrapper && !TRACKING_NAMES.has(name)) {
109
+ // detect wrapper
110
+ if (!isKnownWrapper &&
111
+ isAutoWrapper &&
112
+ !isTrackingByType &&
113
+ !name.startsWith("track")) {
99
114
  detectedFunctionWrappers.add(name);
100
115
  }
101
116
  const paramMap = new Map(inheritedParamMap ?? []);
@@ -114,26 +129,22 @@ function scanSource(source, checker, config, visited = new Set(), depth = 0, inh
114
129
  if (resolvedFn) {
115
130
  const idx = (0, functionAnalyzer_1.findTrackParamIndex)(resolvedFn);
116
131
  if (idx !== null && node.arguments[idx]) {
117
- const res = (0, resolver_1.resolveNodeValue)(node.arguments[idx], checker, paramMap);
118
- if (res && res.values.length) {
119
- res.values.forEach(v => {
120
- if (isValidEvent(v))
121
- events.add(v);
122
- });
123
- }
132
+ const res = (0, resolver_1.resolveNodeValue)(unwrap(node.arguments[idx]), checker, paramMap);
133
+ res.values.forEach(v => {
134
+ if (isValidEvent(v))
135
+ events.add(v);
136
+ });
124
137
  handled = true;
125
138
  }
126
139
  }
127
140
  // FALLBACK
128
141
  if (!handled) {
129
142
  for (const arg of node.arguments) {
130
- const res = (0, resolver_1.resolveNodeValue)(arg, checker, paramMap);
131
- if (res && res.values.length) {
132
- res.values.forEach(v => {
133
- if (isValidEvent(v))
134
- events.add(v);
135
- });
136
- }
143
+ const res = (0, resolver_1.resolveNodeValue)(unwrap(arg), checker, paramMap);
144
+ res.values.forEach(v => {
145
+ if (isValidEvent(v))
146
+ events.add(v);
147
+ });
137
148
  }
138
149
  }
139
150
  // CROSS FILE
@@ -183,7 +194,7 @@ function scanSource(source, checker, config, visited = new Set(), depth = 0, inh
183
194
  continue;
184
195
  for (const el of param.name.elements) {
185
196
  const prop = el.name.getText();
186
- const isEvent = isPropUsedInTracking(fn, prop) ||
197
+ const isEvent = isPropUsedInTracking(fn, checker, prop) ||
187
198
  config.wrappers.some(w => w.name === tagName && w.prop === prop);
188
199
  if (!isEvent)
189
200
  continue;
@@ -0,0 +1,68 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.isTrackingCall = isTrackingCall;
7
+ const typescript_1 = __importDefault(require("typescript"));
8
+ function isStringLike(type) {
9
+ return ((type.flags & typescript_1.default.TypeFlags.StringLike) !== 0 ||
10
+ (type.flags & typescript_1.default.TypeFlags.StringLiteral) !== 0);
11
+ }
12
+ function hasTrackLikeSignature(type, checker) {
13
+ const sigs = type.getCallSignatures();
14
+ for (const sig of sigs) {
15
+ const params = sig.getParameters();
16
+ if (!params.length)
17
+ continue;
18
+ const t = checker.getTypeOfSymbolAtLocation(params[0], params[0].valueDeclaration);
19
+ if (isStringLike(t)) {
20
+ return true;
21
+ }
22
+ }
23
+ return false;
24
+ }
25
+ function isEventraLikeType(type, checker) {
26
+ const props = type.getProperties();
27
+ for (const prop of props) {
28
+ const decl = prop.valueDeclaration ?? prop.declarations?.[0];
29
+ if (!decl)
30
+ continue;
31
+ const t = checker.getTypeOfSymbolAtLocation(prop, decl);
32
+ if (hasTrackLikeSignature(t, checker)) {
33
+ return true;
34
+ }
35
+ }
36
+ return false;
37
+ }
38
+ function isTrackingCall(node, checker) {
39
+ const expr = node.expression;
40
+ // fn(...)
41
+ if (typescript_1.default.isIdentifier(expr)) {
42
+ const type = checker.getTypeAtLocation(expr);
43
+ if (hasTrackLikeSignature(type, checker)) {
44
+ return true;
45
+ }
46
+ }
47
+ // obj.method(...)
48
+ if (typescript_1.default.isPropertyAccessExpression(expr)) {
49
+ const objType = checker.getTypeAtLocation(expr.expression);
50
+ // объект похож на tracker
51
+ if (isEventraLikeType(objType, checker)) {
52
+ return true;
53
+ }
54
+ // fallback
55
+ const methodType = checker.getTypeAtLocation(expr.name);
56
+ if (hasTrackLikeSignature(methodType, checker)) {
57
+ return true;
58
+ }
59
+ }
60
+ // obj["track"](...)
61
+ if (typescript_1.default.isElementAccessExpression(expr)) {
62
+ const objType = checker.getTypeAtLocation(expr.expression);
63
+ if (isEventraLikeType(objType, checker)) {
64
+ return true;
65
+ }
66
+ }
67
+ return false;
68
+ }
@@ -1,20 +1,11 @@
1
1
  "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
2
  Object.defineProperty(exports, "__esModule", { value: true });
6
3
  exports.processFile = processFile;
7
- const path_1 = __importDefault(require("path"));
8
4
  const universal_1 = require("./universal");
9
5
  function processFile(file, content) {
10
6
  const { code, deps } = (0, universal_1.parseUniversal)(content, file);
11
- const ext = path_1.default.extname(file);
12
- const virtualFile = /\.(vue|svelte|astro|html)$/i.test(file)
13
- ? file.replace(ext, "") + ".tsx"
14
- : file;
15
7
  return {
16
8
  content: code || "",
17
- virtualFile,
18
9
  deps: [...new Set(deps.filter(Boolean))],
19
10
  };
20
11
  }