@eventra_dev/eventra-cli 0.1.17 → 0.1.18

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.
@@ -38,17 +38,18 @@ async function check({ fix = false }) {
38
38
  const known = new Set(config.events ?? []);
39
39
  const newEvents = [...found].filter(e => !known.has(e));
40
40
  const removed = [...known].filter(e => !found.has(e));
41
+ // FIX MODE
41
42
  if (fix) {
42
- const merged = new Set(config.events ?? []);
43
- newEvents.forEach(e => {
44
- merged.add(e);
45
- console.log(chalk_1.default.green(`+ ${e}`));
46
- });
47
- config.events = [...merged].sort();
43
+ const next = new Set();
44
+ found.forEach(e => next.add(e));
45
+ newEvents.forEach(e => console.log(chalk_1.default.green(`+ ${e}`)));
46
+ removed.forEach(e => console.log(chalk_1.default.red(`- ${e}`)));
47
+ config.events = [...next].sort();
48
48
  await (0, config_1.saveConfig)(config);
49
- console.log(chalk_1.default.green("\nFixed"));
49
+ console.log(chalk_1.default.green(`\nSynced (${config.events.length} events)`));
50
50
  return;
51
51
  }
52
+ // CHECK MODE
52
53
  if (newEvents.length || removed.length) {
53
54
  if (newEvents.length) {
54
55
  console.log(chalk_1.default.red("\nNew events:"));
@@ -87,6 +87,7 @@ function scanSource(source, checker, config, visited = new Set(), depth = 0, inh
87
87
  const isAutoWrapper = resolvedFn && (0, functionAnalyzer_1.findTrackParamIndex)(resolvedFn) !== null;
88
88
  const isTracking = isKnownWrapper ||
89
89
  TRACKING_NAMES.has(name) ||
90
+ name.startsWith("$") || // NUXT FIX
90
91
  (isAutoWrapper &&
91
92
  resolvedFn &&
92
93
  !(0, boundary_1.isExternalFile)(resolvedFn.getSourceFile().fileName));
@@ -97,7 +98,6 @@ function scanSource(source, checker, config, visited = new Set(), depth = 0, inh
97
98
  if (!isKnownWrapper && isAutoWrapper && !TRACKING_NAMES.has(name)) {
98
99
  detectedFunctionWrappers.add(name);
99
100
  }
100
- // paramMap (chain support)
101
101
  const paramMap = new Map(inheritedParamMap ?? []);
102
102
  if (resolvedFn && typescript_1.default.isFunctionLike(resolvedFn)) {
103
103
  resolvedFn.parameters.forEach((param, i) => {
@@ -110,6 +110,7 @@ function scanSource(source, checker, config, visited = new Set(), depth = 0, inh
110
110
  });
111
111
  }
112
112
  let handled = false;
113
+ // MAIN RESOLVE
113
114
  if (resolvedFn) {
114
115
  const idx = (0, functionAnalyzer_1.findTrackParamIndex)(resolvedFn);
115
116
  if (idx !== null && node.arguments[idx]) {
@@ -121,6 +122,7 @@ function scanSource(source, checker, config, visited = new Set(), depth = 0, inh
121
122
  handled = true;
122
123
  }
123
124
  }
125
+ // FALLBACK
124
126
  if (!handled) {
125
127
  for (const arg of node.arguments) {
126
128
  const res = (0, resolver_1.resolveNodeValue)(arg, checker, paramMap);
@@ -130,7 +132,7 @@ function scanSource(source, checker, config, visited = new Set(), depth = 0, inh
130
132
  });
131
133
  }
132
134
  }
133
- // cross-file with paramMap
135
+ // CROSS FILE
134
136
  if (resolvedFn) {
135
137
  const sf = resolvedFn.getSourceFile();
136
138
  if (!visited.has(sf.fileName)) {
@@ -1,32 +1,33 @@
1
1
  "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
2
5
  Object.defineProperty(exports, "__esModule", { value: true });
3
6
  exports.parseAstro = parseAstro;
4
- function parseAstro(content) {
7
+ const path_1 = __importDefault(require("path"));
8
+ function parseAstro(content, file) {
5
9
  const parts = [];
6
10
  // frontmatter
7
11
  const frontmatter = content.match(/^---([\s\S]*?)---/);
8
12
  if (frontmatter?.[1]) {
9
13
  parts.push(frontmatter[1]);
10
14
  }
11
- // scripts
12
15
  const scripts = content.matchAll(/<script([^>]*)>([\s\S]*?)<\/script>/g);
13
16
  for (const m of scripts) {
14
17
  const attrs = m[1];
15
18
  const body = m[2];
16
19
  if (attrs.includes("src")) {
17
20
  const src = attrs.match(/src=["'](.+?)["']/)?.[1];
18
- if (src) {
19
- parts.push(`import "${src}"`);
20
- }
21
+ if (!src)
22
+ continue;
23
+ const resolved = src.startsWith("/")
24
+ ? path_1.default.resolve(process.cwd(), src.slice(1))
25
+ : path_1.default.resolve(path_1.default.dirname(file), src);
26
+ parts.push(`import "${resolved}"`);
21
27
  }
22
28
  else {
23
29
  parts.push(body);
24
30
  }
25
31
  }
26
- // inline handlers
27
- const inlineJS = content.matchAll(/on\w+="([^"]+)"/g);
28
- for (const m of inlineJS) {
29
- parts.push(m[1] + ";");
30
- }
31
32
  return parts.join("\n");
32
33
  }
@@ -1,13 +1,17 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.detectParser = detectParser;
4
+ const vue_1 = require("./vue");
5
+ const svelte_1 = require("./svelte");
6
+ const astro_1 = require("./astro");
7
+ const fallback = (content) => content;
4
8
  function detectParser(file) {
5
- file = file.toLowerCase();
6
- if (file.endsWith(".vue"))
7
- return "vue";
8
- if (file.endsWith(".svelte"))
9
- return "svelte";
10
- if (file.endsWith(".astro"))
11
- return "astro";
12
- return "ts";
9
+ const f = file.toLowerCase();
10
+ if (f.endsWith(".vue"))
11
+ return vue_1.parseVue;
12
+ if (f.endsWith(".svelte"))
13
+ return svelte_1.parseSvelte;
14
+ if (f.endsWith(".astro"))
15
+ return astro_1.parseAstro;
16
+ return fallback;
13
17
  }
@@ -1,17 +1,31 @@
1
1
  "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
2
5
  Object.defineProperty(exports, "__esModule", { value: true });
3
6
  exports.parseSvelte = parseSvelte;
4
- function parseSvelte(content) {
7
+ const path_1 = __importDefault(require("path"));
8
+ function parseSvelte(content, file) {
5
9
  const parts = [];
6
- // scripts
7
- const scripts = content.matchAll(/<script[^>]*>([\s\S]*?)<\/script>/g);
10
+ const scripts = content.matchAll(/<script([^>]*)>([\s\S]*?)<\/script>/g);
8
11
  for (const m of scripts) {
9
- parts.push(m[1]);
12
+ const attrs = m[1];
13
+ const body = m[2];
14
+ if (attrs.includes("src")) {
15
+ const src = attrs.match(/src=["'](.+?)["']/)?.[1];
16
+ if (!src)
17
+ continue;
18
+ const resolved = src.startsWith("/")
19
+ ? path_1.default.resolve(process.cwd(), src.slice(1))
20
+ : path_1.default.resolve(path_1.default.dirname(file), src);
21
+ parts.push(`import "${resolved}"`);
22
+ continue;
23
+ }
24
+ parts.push(body);
10
25
  }
11
- // on:click / on:*
12
- const handlers = content.matchAll(/on:\w+=\{([^}]+)\}/g);
13
- for (const h of handlers) {
14
- parts.push(h[1] + ";");
26
+ // handlers
27
+ for (const h of content.matchAll(/on:\w+=\{([^}]+)\}/g)) {
28
+ parts.push(`${h[1]}();`);
15
29
  }
16
30
  return parts.join("\n");
17
31
  }
@@ -1,41 +1,41 @@
1
1
  "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
2
5
  Object.defineProperty(exports, "__esModule", { value: true });
3
6
  exports.parseVue = parseVue;
4
- function parseVue(content) {
7
+ const path_1 = __importDefault(require("path"));
8
+ function parseVue(content, file) {
5
9
  const parts = [];
6
- // --- SCRIPT ---
7
10
  const scripts = content.matchAll(/<script([^>]*)>([\s\S]*?)<\/script>/g);
8
11
  for (const m of scripts) {
9
12
  const attrs = m[1];
10
13
  const body = m[2];
14
+ // src
11
15
  if (attrs.includes("src")) {
12
16
  const src = attrs.match(/src=["'](.+?)["']/)?.[1];
13
- if (src) {
14
- parts.push(`import "${src}"`);
15
- }
17
+ if (!src)
18
+ continue;
19
+ const resolved = src.startsWith("/")
20
+ ? path_1.default.resolve(process.cwd(), src.slice(1))
21
+ : path_1.default.resolve(path_1.default.dirname(file), src);
22
+ parts.push(`import "${resolved}"`);
16
23
  continue;
17
24
  }
25
+ // script setup
18
26
  parts.push(body);
19
27
  }
20
- // --- TEMPLATE ---
28
+ // TEMPLATE
21
29
  const templateMatch = content.match(/<template[^>]*>([\s\S]*?)<\/template>/);
22
30
  let template = templateMatch?.[1] ?? "";
23
- // remove comments
24
31
  template = template.replace(/<!--[\s\S]*?-->/g, "");
25
- // --- event handlers (@click)
26
- const handlers = template.matchAll(/@[\w-]+="([^"]+)"/g);
27
- for (const h of handlers) {
28
- parts.push(h[1] + ";");
32
+ // @click
33
+ for (const m of template.matchAll(/@[\w-]+="([^"]+)"/g)) {
34
+ parts.push(`${m[1]}();`);
29
35
  }
30
- // --- v-on
31
- const vOn = template.matchAll(/v-on:[\w-]+="([^"]+)"/g);
32
- for (const v of vOn) {
33
- parts.push(v[1] + ";");
34
- }
35
- // --- :prop bindings
36
- const binds = template.matchAll(/:[\w-]+="([^"]+)"/g);
37
- for (const b of binds) {
38
- parts.push(b[1] + ";");
36
+ // v-on
37
+ for (const m of template.matchAll(/v-on:[\w-]+="([^"]+)"/g)) {
38
+ parts.push(`${m[1]}();`);
39
39
  }
40
40
  return parts.join("\n");
41
41
  }
@@ -1,22 +1,18 @@
1
1
  "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
2
5
  Object.defineProperty(exports, "__esModule", { value: true });
3
6
  exports.processFile = processFile;
7
+ const path_1 = __importDefault(require("path"));
4
8
  const router_1 = require("./parsers/router");
5
- const vue_1 = require("./parsers/vue");
6
- const svelte_1 = require("./parsers/svelte");
7
- const astro_1 = require("./parsers/astro");
8
9
  function processFile(file, content) {
9
- const parser = (0, router_1.detectParser)(file);
10
- if (parser === "vue")
11
- content = (0, vue_1.parseVue)(content);
12
- if (parser === "svelte")
13
- content = (0, svelte_1.parseSvelte)(content);
14
- if (parser === "astro")
15
- content = (0, astro_1.parseAstro)(content);
16
- const virtualFile = file.endsWith(".vue") ||
10
+ const abs = path_1.default.resolve(file);
11
+ const parser = (0, router_1.detectParser)(abs);
12
+ content = parser(content, abs);
13
+ const isVirtual = file.endsWith(".vue") ||
17
14
  file.endsWith(".svelte") ||
18
- file.endsWith(".astro")
19
- ? file + ".tsx"
20
- : file;
15
+ file.endsWith(".astro");
16
+ const virtualFile = isVirtual ? abs + ".tsx" : abs;
21
17
  return { content, virtualFile };
22
18
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eventra_dev/eventra-cli",
3
- "version": "0.1.17",
3
+ "version": "0.1.18",
4
4
  "description": "Eventra CLI",
5
5
  "type": "commonjs",
6
6
  "bin": {
@@ -25,7 +25,6 @@ export async function check({ fix = false }: { fix?: boolean }) {
25
25
  for (const file of files) {
26
26
  try {
27
27
  const raw = await fs.readFile(file, "utf-8");
28
-
29
28
  const { content, virtualFile } = processFile(file, raw);
30
29
 
31
30
  engine.scanFile(virtualFile, content, config);
@@ -45,31 +44,45 @@ export async function check({ fix = false }: { fix?: boolean }) {
45
44
  const newEvents = [...found].filter(e => !known.has(e));
46
45
  const removed = [...known].filter(e => !found.has(e));
47
46
 
47
+ // FIX MODE
48
48
  if (fix) {
49
- const merged = new Set(config.events ?? []);
49
+ const next = new Set<string>();
50
+
51
+ found.forEach(e => next.add(e));
50
52
 
51
- newEvents.forEach(e => {
52
- merged.add(e);
53
- console.log(chalk.green(`+ ${e}`));
54
- });
53
+ newEvents.forEach(e =>
54
+ console.log(chalk.green(`+ ${e}`))
55
+ );
55
56
 
56
- config.events = [...merged].sort();
57
+ removed.forEach(e =>
58
+ console.log(chalk.red(`- ${e}`))
59
+ );
60
+
61
+ config.events = [...next].sort();
57
62
 
58
63
  await saveConfig(config);
59
64
 
60
- console.log(chalk.green("\nFixed"));
65
+ console.log(
66
+ chalk.green(`\nSynced (${config.events.length} events)`)
67
+ );
68
+
61
69
  return;
62
70
  }
63
71
 
72
+ // CHECK MODE
64
73
  if (newEvents.length || removed.length) {
65
74
  if (newEvents.length) {
66
75
  console.log(chalk.red("\nNew events:"));
67
- newEvents.forEach(e => console.log(chalk.red(`+ ${e}`)));
76
+ newEvents.forEach(e =>
77
+ console.log(chalk.red(`+ ${e}`))
78
+ );
68
79
  }
69
80
 
70
81
  if (removed.length) {
71
82
  console.log(chalk.yellow("\nRemoved events:"));
72
- removed.forEach(e => console.log(chalk.yellow(`- ${e}`)));
83
+ removed.forEach(e =>
84
+ console.log(chalk.yellow(`- ${e}`))
85
+ );
73
86
  }
74
87
 
75
88
  process.exit(1);
@@ -126,6 +126,7 @@ export function scanSource(
126
126
  const isTracking =
127
127
  isKnownWrapper ||
128
128
  TRACKING_NAMES.has(name) ||
129
+ name.startsWith("$") || // NUXT FIX
129
130
  (isAutoWrapper &&
130
131
  resolvedFn &&
131
132
  !isExternalFile(resolvedFn.getSourceFile().fileName));
@@ -139,7 +140,6 @@ export function scanSource(
139
140
  detectedFunctionWrappers.add(name);
140
141
  }
141
142
 
142
- // paramMap (chain support)
143
143
  const paramMap = new Map(inheritedParamMap ?? []);
144
144
 
145
145
  if (resolvedFn && ts.isFunctionLike(resolvedFn)) {
@@ -155,6 +155,7 @@ export function scanSource(
155
155
 
156
156
  let handled = false;
157
157
 
158
+ // MAIN RESOLVE
158
159
  if (resolvedFn) {
159
160
  const idx = findTrackParamIndex(resolvedFn);
160
161
 
@@ -173,6 +174,7 @@ export function scanSource(
173
174
  }
174
175
  }
175
176
 
177
+ // FALLBACK
176
178
  if (!handled) {
177
179
  for (const arg of node.arguments) {
178
180
  const res = resolveNodeValue(arg, checker, paramMap);
@@ -183,7 +185,7 @@ export function scanSource(
183
185
  }
184
186
  }
185
187
 
186
- // cross-file with paramMap
188
+ // CROSS FILE
187
189
  if (resolvedFn) {
188
190
  const sf = resolvedFn.getSourceFile();
189
191
 
package/src/types.ts CHANGED
@@ -1,5 +1,7 @@
1
1
  export type ScanMode = "strict" | "hybrid" | "discovery";
2
2
 
3
+ export type Parser = (content: string, file: string) => string;
4
+
3
5
  export type ComponentWrapper = {
4
6
  name: string;
5
7
  prop: string;
@@ -23,8 +25,3 @@ export type EventraConfig = {
23
25
  exclude: string[];
24
26
  };
25
27
  };
26
-
27
- export type ResolveResult = {
28
- values: string[];
29
- dynamic: boolean;
30
- };
@@ -1,4 +1,6 @@
1
- export function parseAstro(content: string) {
1
+ import path from "path";
2
+
3
+ export function parseAstro(content: string, file: string) {
2
4
  const parts: string[] = [];
3
5
 
4
6
  // frontmatter
@@ -7,7 +9,6 @@ export function parseAstro(content: string) {
7
9
  parts.push(frontmatter[1]);
8
10
  }
9
11
 
10
- // scripts
11
12
  const scripts = content.matchAll(/<script([^>]*)>([\s\S]*?)<\/script>/g);
12
13
 
13
14
  for (const m of scripts) {
@@ -16,20 +17,17 @@ export function parseAstro(content: string) {
16
17
 
17
18
  if (attrs.includes("src")) {
18
19
  const src = attrs.match(/src=["'](.+?)["']/)?.[1];
19
- if (src) {
20
- parts.push(`import "${src}"`);
21
- }
20
+ if (!src) continue;
21
+
22
+ const resolved = src.startsWith("/")
23
+ ? path.resolve(process.cwd(), src.slice(1))
24
+ : path.resolve(path.dirname(file), src);
25
+
26
+ parts.push(`import "${resolved}"`);
22
27
  } else {
23
28
  parts.push(body);
24
29
  }
25
30
  }
26
31
 
27
- // inline handlers
28
- const inlineJS = content.matchAll(/on\w+="([^"]+)"/g);
29
-
30
- for (const m of inlineJS) {
31
- parts.push(m[1] + ";");
32
- }
33
-
34
32
  return parts.join("\n");
35
33
  }
@@ -1,9 +1,16 @@
1
- export function detectParser(file: string) {
2
- file = file.toLowerCase();
1
+ import { Parser } from "../../types";
2
+ import { parseVue } from "./vue";
3
+ import { parseSvelte } from "./svelte";
4
+ import { parseAstro } from "./astro";
3
5
 
4
- if (file.endsWith(".vue")) return "vue";
5
- if (file.endsWith(".svelte")) return "svelte";
6
- if (file.endsWith(".astro")) return "astro";
6
+ const fallback: Parser = (content) => content;
7
7
 
8
- return "ts";
8
+ export function detectParser(file: string): Parser {
9
+ const f = file.toLowerCase();
10
+
11
+ if (f.endsWith(".vue")) return parseVue;
12
+ if (f.endsWith(".svelte")) return parseSvelte;
13
+ if (f.endsWith(".astro")) return parseAstro;
14
+
15
+ return fallback;
9
16
  }
@@ -1,16 +1,32 @@
1
- export function parseSvelte(content: string) {
1
+ import path from "path";
2
+
3
+ export function parseSvelte(content: string, file: string) {
2
4
  const parts: string[] = [];
3
5
 
4
- // scripts
5
- const scripts = content.matchAll(/<script[^>]*>([\s\S]*?)<\/script>/g);
6
+ const scripts = content.matchAll(/<script([^>]*)>([\s\S]*?)<\/script>/g);
7
+
6
8
  for (const m of scripts) {
7
- parts.push(m[1]);
9
+ const attrs = m[1];
10
+ const body = m[2];
11
+
12
+ if (attrs.includes("src")) {
13
+ const src = attrs.match(/src=["'](.+?)["']/)?.[1];
14
+ if (!src) continue;
15
+
16
+ const resolved = src.startsWith("/")
17
+ ? path.resolve(process.cwd(), src.slice(1))
18
+ : path.resolve(path.dirname(file), src);
19
+
20
+ parts.push(`import "${resolved}"`);
21
+ continue;
22
+ }
23
+
24
+ parts.push(body);
8
25
  }
9
26
 
10
- // on:click / on:*
11
- const handlers = content.matchAll(/on:\w+=\{([^}]+)\}/g);
12
- for (const h of handlers) {
13
- parts.push(h[1] + ";");
27
+ // handlers
28
+ for (const h of content.matchAll(/on:\w+=\{([^}]+)\}/g)) {
29
+ parts.push(`${h[1]}();`);
14
30
  }
15
31
 
16
32
  return parts.join("\n");
@@ -1,47 +1,45 @@
1
- export function parseVue(content: string) {
1
+ import path from "path";
2
+
3
+ export function parseVue(content: string, file: string) {
2
4
  const parts: string[] = [];
3
5
 
4
- // --- SCRIPT ---
5
6
  const scripts = content.matchAll(/<script([^>]*)>([\s\S]*?)<\/script>/g);
6
7
 
7
8
  for (const m of scripts) {
8
9
  const attrs = m[1];
9
10
  const body = m[2];
10
11
 
12
+ // src
11
13
  if (attrs.includes("src")) {
12
14
  const src = attrs.match(/src=["'](.+?)["']/)?.[1];
13
- if (src) {
14
- parts.push(`import "${src}"`);
15
- }
15
+ if (!src) continue;
16
+
17
+ const resolved = src.startsWith("/")
18
+ ? path.resolve(process.cwd(), src.slice(1))
19
+ : path.resolve(path.dirname(file), src);
20
+
21
+ parts.push(`import "${resolved}"`);
16
22
  continue;
17
23
  }
18
24
 
25
+ // script setup
19
26
  parts.push(body);
20
27
  }
21
28
 
22
- // --- TEMPLATE ---
29
+ // TEMPLATE
23
30
  const templateMatch = content.match(/<template[^>]*>([\s\S]*?)<\/template>/);
24
31
  let template = templateMatch?.[1] ?? "";
25
32
 
26
- // remove comments
27
33
  template = template.replace(/<!--[\s\S]*?-->/g, "");
28
34
 
29
- // --- event handlers (@click)
30
- const handlers = template.matchAll(/@[\w-]+="([^"]+)"/g);
31
- for (const h of handlers) {
32
- parts.push(h[1] + ";");
33
- }
34
-
35
- // --- v-on
36
- const vOn = template.matchAll(/v-on:[\w-]+="([^"]+)"/g);
37
- for (const v of vOn) {
38
- parts.push(v[1] + ";");
35
+ // @click
36
+ for (const m of template.matchAll(/@[\w-]+="([^"]+)"/g)) {
37
+ parts.push(`${m[1]}();`);
39
38
  }
40
39
 
41
- // --- :prop bindings
42
- const binds = template.matchAll(/:[\w-]+="([^"]+)"/g);
43
- for (const b of binds) {
44
- parts.push(b[1] + ";");
40
+ // v-on
41
+ for (const m of template.matchAll(/v-on:[\w-]+="([^"]+)"/g)) {
42
+ parts.push(`${m[1]}();`);
45
43
  }
46
44
 
47
45
  return parts.join("\n");
@@ -1,21 +1,18 @@
1
+ import path from "path";
1
2
  import { detectParser } from "./parsers/router";
2
- import { parseVue } from "./parsers/vue";
3
- import { parseSvelte } from "./parsers/svelte";
4
- import { parseAstro } from "./parsers/astro";
5
3
 
6
4
  export function processFile(file: string, content: string) {
7
- const parser = detectParser(file);
5
+ const abs = path.resolve(file);
8
6
 
9
- if (parser === "vue") content = parseVue(content);
10
- if (parser === "svelte") content = parseSvelte(content);
11
- if (parser === "astro") content = parseAstro(content);
7
+ const parser = detectParser(abs);
8
+ content = parser(content, abs);
12
9
 
13
- const virtualFile =
10
+ const isVirtual =
14
11
  file.endsWith(".vue") ||
15
12
  file.endsWith(".svelte") ||
16
- file.endsWith(".astro")
17
- ? file + ".tsx"
18
- : file;
13
+ file.endsWith(".astro");
14
+
15
+ const virtualFile = isVirtual ? abs + ".tsx" : abs;
19
16
 
20
17
  return { content, virtualFile };
21
18
  }