@eventra_dev/eventra-cli 0.1.1 → 0.1.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 (45) hide show
  1. package/README.md +48 -73
  2. package/dist/commands/check.js +18 -7
  3. package/dist/commands/send.js +7 -1
  4. package/dist/commands/sync.js +5 -3
  5. package/dist/commands/watch.js +13 -55
  6. package/dist/engine/callResolver.js +15 -24
  7. package/dist/engine/engine.js +32 -7
  8. package/dist/engine/graph.js +50 -9
  9. package/dist/engine/hash.js +10 -0
  10. package/dist/engine/languageService.js +74 -0
  11. package/dist/engine/resolver.js +88 -24
  12. package/dist/engine/scanner.js +57 -67
  13. package/dist/utils/config.js +7 -2
  14. package/dist/utils/parsers/astro.js +8 -1
  15. package/dist/utils/parsers/router.js +1 -0
  16. package/dist/utils/parsers/svelte.js +4 -1
  17. package/dist/utils/parsers/vue.js +13 -4
  18. package/dist/utils/processFile.js +3 -1
  19. package/package.json +1 -1
  20. package/src/commands/check.ts +21 -7
  21. package/src/commands/send.ts +8 -2
  22. package/src/commands/sync.ts +6 -3
  23. package/src/commands/watch.ts +14 -60
  24. package/src/engine/callResolver.ts +21 -32
  25. package/src/engine/engine.ts +40 -7
  26. package/src/engine/graph.ts +58 -11
  27. package/src/engine/hash.ts +5 -0
  28. package/src/engine/languageService.ts +103 -0
  29. package/src/engine/resolver.ts +104 -24
  30. package/src/engine/scanner.ts +67 -74
  31. package/src/types.ts +5 -0
  32. package/src/utils/config.ts +15 -33
  33. package/src/utils/parsers/astro.ts +11 -4
  34. package/src/utils/parsers/router.ts +5 -10
  35. package/src/utils/parsers/svelte.ts +6 -4
  36. package/src/utils/parsers/vue.ts +15 -5
  37. package/src/utils/processFile.ts +6 -3
  38. package/dist/engine/extract.js +0 -53
  39. package/dist/engine/flow.js +0 -38
  40. package/dist/engine/project.js +0 -74
  41. package/dist/engine/symbol.js +0 -31
  42. package/src/engine/extract.ts +0 -74
  43. package/src/engine/flow.ts +0 -54
  44. package/src/engine/project.ts +0 -75
  45. package/src/engine/symbol.ts +0 -42
@@ -1,26 +1,57 @@
1
- import { ProjectGraph } from "./project";
1
+ import { TSService } from "./languageService";
2
2
  import { scanSource } from "./scanner";
3
+ import { DependencyGraph } from "./graph";
4
+ import { hash } from "./hash";
3
5
 
4
6
  export class EventraEngine {
5
- private project: ProjectGraph;
7
+ private ts: TSService;
8
+ private graph = new DependencyGraph();
9
+
6
10
  private fileEvents = new Map<string, Set<string>>();
11
+ private fileHash = new Map<string, string>();
7
12
 
8
- constructor(entry: string) {
9
- this.project = new ProjectGraph(entry);
13
+ constructor(root: string) {
14
+ this.ts = new TSService(root);
10
15
  }
11
16
 
12
17
  scanFile(file: string, content: string) {
13
- const source = this.project.getSource(file, content);
14
- const checker = this.project.getChecker();
18
+ const h = hash(content);
19
+
20
+ if (this.fileHash.get(file) === h) {
21
+ return this.fileEvents.get(file) ?? new Set();
22
+ }
23
+
24
+ this.fileHash.set(file, h);
25
+ this.ts.updateFile(file, content);
15
26
 
27
+ const source = this.ts.getSourceFile(file);
28
+ const checker = this.ts.getChecker();
29
+
30
+ if (!source || !checker) return new Set();
31
+
32
+ this.graph.update(file, source);
16
33
  const events = scanSource(source, checker);
17
34
 
18
35
  this.fileEvents.set(file, events);
36
+
19
37
  return events;
20
38
  }
21
39
 
22
40
  updateFile(file: string, content: string) {
23
- return this.scanFile(file, content);
41
+ const events = this.scanFile(file, content);
42
+ const dependents = this.graph.getDependents(file);
43
+
44
+ for (const dep of dependents) {
45
+ const source = this.ts.getSourceFile(dep);
46
+ const checker = this.ts.getChecker();
47
+
48
+ if (!source || !checker) continue;
49
+
50
+ const ev = scanSource(source, checker);
51
+ this.fileEvents.set(dep, ev);
52
+ }
53
+
54
+ return events;
24
55
  }
25
56
 
26
57
  getAllEvents() {
@@ -35,5 +66,7 @@ export class EventraEngine {
35
66
 
36
67
  removeFile(file: string) {
37
68
  this.fileEvents.delete(file);
69
+ this.fileHash.delete(file);
70
+ this.graph.remove(file);
38
71
  }
39
72
  }
@@ -1,23 +1,70 @@
1
- import { SourceFile } from "ts-morph";
1
+ import ts from "typescript";
2
+ import path from "path";
3
+ import fs from "fs";
2
4
 
3
5
  export class DependencyGraph {
4
6
  private reverse = new Map<string, Set<string>>();
5
7
 
6
- update(file: string, source: SourceFile) {
7
- const imports = source
8
- .getImportDeclarations()
9
- .map(i => i.getModuleSpecifierSourceFile()?.getFilePath())
10
- .filter(Boolean) as string[];
8
+ private normalize(file: string) {
9
+ return path.resolve(file);
10
+ }
11
+
12
+ private resolveImport(from: string, module: string): string | null {
13
+ if (!module.startsWith(".")) return null;
14
+
15
+ const base = path.resolve(path.dirname(from), module);
11
16
 
12
- for (const dep of imports) {
13
- if (!this.reverse.has(dep)) {
14
- this.reverse.set(dep, new Set());
17
+ const tryFiles = [
18
+ base,
19
+ base + ".ts",
20
+ base + ".tsx",
21
+ base + ".js",
22
+ base + ".jsx",
23
+ path.join(base, "index.ts"),
24
+ path.join(base, "index.tsx"),
25
+ path.join(base, "index.js"),
26
+ ];
27
+
28
+ for (const f of tryFiles) {
29
+ if (fs.existsSync(f)) {
30
+ return this.normalize(f);
15
31
  }
16
- this.reverse.get(dep)!.add(file);
32
+ }
33
+
34
+ return this.normalize(base);
35
+ }
36
+
37
+ update(file: string, source: ts.SourceFile) {
38
+ const normalizedFile = this.normalize(file);
39
+
40
+ for (const stmt of source.statements) {
41
+ if (!ts.isImportDeclaration(stmt)) continue;
42
+
43
+ const module = stmt.moduleSpecifier
44
+ .getText()
45
+ .replace(/['"]/g, "");
46
+
47
+ const resolved = this.resolveImport(normalizedFile, module);
48
+
49
+ if (!resolved) continue;
50
+
51
+ if (!this.reverse.has(resolved)) {
52
+ this.reverse.set(resolved, new Set());
53
+ }
54
+
55
+ this.reverse.get(resolved)!.add(normalizedFile);
17
56
  }
18
57
  }
19
58
 
20
59
  getDependents(file: string) {
21
- return [...(this.reverse.get(file) ?? [])];
60
+ return [...(this.reverse.get(this.normalize(file)) ?? [])];
61
+ }
62
+
63
+ remove(file: string) {
64
+ const normalized = this.normalize(file);
65
+
66
+ for (const set of this.reverse.values()) {
67
+ set.delete(normalized);
68
+ }
22
69
  }
23
70
  }
@@ -0,0 +1,5 @@
1
+ import crypto from "crypto";
2
+
3
+ export function hash(content: string) {
4
+ return crypto.createHash("md5").update(content).digest("hex");
5
+ }
@@ -0,0 +1,103 @@
1
+ import ts from "typescript";
2
+ import fs from "fs";
3
+
4
+ export class TSService {
5
+ private files = new Map<string, { version: number; content: string }>();
6
+ private service: ts.LanguageService;
7
+
8
+ constructor(private root: string) {
9
+ const configPath = ts.findConfigFile(
10
+ root,
11
+ ts.sys.fileExists,
12
+ "tsconfig.json"
13
+ );
14
+
15
+ let compilerOptions: ts.CompilerOptions = {
16
+ allowJs: true,
17
+ jsx: ts.JsxEmit.Preserve,
18
+ moduleResolution: ts.ModuleResolutionKind.Node16,
19
+ target: ts.ScriptTarget.ESNext,
20
+ esModuleInterop: true,
21
+ allowSyntheticDefaultImports: true,
22
+ };
23
+
24
+ if (configPath) {
25
+ const configFile = ts.readConfigFile(
26
+ configPath,
27
+ ts.sys.readFile
28
+ );
29
+
30
+ const parsed = ts.parseJsonConfigFileContent(
31
+ configFile.config,
32
+ ts.sys,
33
+ root
34
+ );
35
+
36
+ compilerOptions = {
37
+ ...compilerOptions,
38
+ ...parsed.options,
39
+ };
40
+ }
41
+
42
+ const host: ts.LanguageServiceHost = {
43
+ getScriptFileNames: () => [
44
+ ...this.files.keys(),
45
+ ...ts.sys.readDirectory(
46
+ this.root,
47
+ [".ts", ".tsx"],
48
+ ["node_modules", "dist", ".git"]
49
+ )
50
+ ],
51
+ getScriptVersion: (file) =>
52
+ this.files.get(file)?.version.toString() ?? "0",
53
+
54
+ getScriptSnapshot: (file) => {
55
+ const f = this.files.get(file);
56
+
57
+ if (f) {
58
+ return ts.ScriptSnapshot.fromString(f.content);
59
+ }
60
+
61
+ if (fs.existsSync(file)) {
62
+ return ts.ScriptSnapshot.fromString(
63
+ fs.readFileSync(file, "utf-8")
64
+ );
65
+ }
66
+
67
+ return undefined;
68
+ },
69
+
70
+ getCurrentDirectory: () => this.root,
71
+ getCompilationSettings: () => compilerOptions,
72
+ getDefaultLibFileName: (opts) =>
73
+ ts.getDefaultLibFilePath(opts),
74
+
75
+ readFile: ts.sys.readFile,
76
+ fileExists: ts.sys.fileExists,
77
+ readDirectory: ts.sys.readDirectory,
78
+ };
79
+
80
+ this.service = ts.createLanguageService(host);
81
+ }
82
+
83
+ updateFile(file: string, content: string) {
84
+ const prev = this.files.get(file);
85
+
86
+ this.files.set(file, {
87
+ content,
88
+ version: prev ? prev.version + 1 : 1,
89
+ });
90
+ }
91
+
92
+ getProgram() {
93
+ return this.service.getProgram();
94
+ }
95
+
96
+ getChecker() {
97
+ return this.getProgram()?.getTypeChecker();
98
+ }
99
+
100
+ getSourceFile(file: string) {
101
+ return this.getProgram()?.getSourceFile(file);
102
+ }
103
+ }
@@ -1,4 +1,4 @@
1
- import { Node, ts } from "ts-morph";
1
+ import ts from "typescript";
2
2
 
3
3
  export type ResolveResult = {
4
4
  values: string[];
@@ -6,45 +6,125 @@ export type ResolveResult = {
6
6
  };
7
7
 
8
8
  export function resolveNodeValue(
9
- node: Node,
10
- checker: ts.TypeChecker
9
+ node: ts.Node,
10
+ checker: ts.TypeChecker,
11
+ paramMap?: Map<string, ts.Expression>,
12
+ seen = new Set<ts.Node>()
11
13
  ): ResolveResult | null {
12
- if (!Node.isIdentifier(node)) return null;
13
14
 
14
- const symbol = checker.getSymbolAtLocation(node.compilerNode);
15
- if (!symbol) return null;
15
+ if (seen.has(node)) return null;
16
+ seen.add(node);
16
17
 
17
- const decls = symbol.getDeclarations() ?? [];
18
+ // param mapping
19
+ if (ts.isIdentifier(node) && paramMap?.has(node.text)) {
20
+ return resolveNodeValue(
21
+ paramMap.get(node.text)!,
22
+ checker,
23
+ paramMap
24
+ );
25
+ }
26
+
27
+ if (ts.isStringLiteral(node)) {
28
+ return { values: [node.text], dynamic: false };
29
+ }
30
+
31
+ if (ts.isTemplateExpression(node)) {
32
+ let result = node.head.text;
33
+
34
+ for (const span of node.templateSpans) {
35
+ result += "*";
36
+ result += span.literal.text;
37
+ }
38
+
39
+ return { values: [result], dynamic: true };
40
+ }
41
+
42
+ if (ts.isConditionalExpression(node)) {
43
+ const a = resolveNodeValue(node.whenTrue, checker, paramMap, seen);
44
+ const b = resolveNodeValue(node.whenFalse, checker, paramMap, seen);
45
+
46
+ return {
47
+ values: [...new Set([...(a?.values ?? []), ...(b?.values ?? [])])],
48
+ dynamic: true,
49
+ };
50
+ }
18
51
 
19
- for (const d of decls) {
20
- if (ts.isVariableDeclaration(d) && d.initializer) {
21
- const init = d.initializer;
52
+ if (ts.isIdentifier(node)) {
53
+ let symbol = checker.getSymbolAtLocation(node);
54
+ if (!symbol) return null;
22
55
 
23
- if (ts.isStringLiteral(init)) {
24
- return { values: [init.text], dynamic: false };
56
+ if (symbol.flags & ts.SymbolFlags.Alias) {
57
+ symbol = checker.getAliasedSymbol(symbol);
58
+ }
59
+
60
+ for (const decl of symbol.getDeclarations() ?? []) {
61
+ if (ts.isVariableDeclaration(decl) && decl.initializer) {
62
+ return resolveNodeValue(decl.initializer, checker, paramMap, seen);
25
63
  }
26
64
 
27
- if (ts.isNoSubstitutionTemplateLiteral(init)) {
28
- return { values: [init.text], dynamic: false };
65
+ if (ts.isEnumMember(decl) && decl.initializer) {
66
+ if (ts.isStringLiteral(decl.initializer)) {
67
+ return {
68
+ values: [decl.initializer.text],
69
+ dynamic: false,
70
+ };
71
+ }
29
72
  }
73
+ }
74
+ }
30
75
 
31
- if (ts.isTemplateExpression(init)) {
32
- let result = init.head.text;
76
+ if (ts.isPropertyAccessExpression(node)) {
77
+ const symbol = checker.getSymbolAtLocation(node);
33
78
 
34
- for (const span of init.templateSpans) {
35
- result += "*";
36
- result += span.literal.text;
79
+ if (symbol) {
80
+ let s = symbol;
81
+
82
+ if (s.flags & ts.SymbolFlags.Alias) {
83
+ s = checker.getAliasedSymbol(s);
84
+ }
85
+
86
+ for (const decl of s.getDeclarations() ?? []) {
87
+ if (ts.isEnumMember(decl) && decl.initializer) {
88
+ if (ts.isStringLiteral(decl.initializer)) {
89
+ return {
90
+ values: [decl.initializer.text],
91
+ dynamic: false,
92
+ };
93
+ }
37
94
  }
95
+ }
96
+ }
97
+ }
98
+
99
+ if (ts.isObjectLiteralExpression(node)) {
100
+ const values: string[] = [];
101
+
102
+ for (const prop of node.properties) {
103
+ if (!ts.isPropertyAssignment(prop)) continue;
38
104
 
39
- return { values: [result], dynamic: true };
105
+ const name = prop.name.getText();
106
+
107
+ if (name === "event") {
108
+ const res = resolveNodeValue(prop.initializer, checker, paramMap, seen);
109
+ if (res) values.push(...res.values);
40
110
  }
41
111
  }
42
112
 
43
- if (ts.isParameter(d) && d.initializer) {
44
- const init = d.initializer;
113
+ return { values, dynamic: true };
114
+ }
115
+
116
+ if (ts.isBinaryExpression(node)) {
117
+ if (node.operatorToken.kind === ts.SyntaxKind.PlusToken) {
118
+ const left = resolveNodeValue(node.left, checker, paramMap, seen);
119
+ const right = resolveNodeValue(node.right, checker, paramMap, seen);
45
120
 
46
- if (ts.isStringLiteral(init)) {
47
- return { values: [init.text], dynamic: false };
121
+ if (left && right) {
122
+ return {
123
+ values: left.values.flatMap(l =>
124
+ right.values.map(r => l + r)
125
+ ),
126
+ dynamic: true,
127
+ };
48
128
  }
49
129
  }
50
130
  }
@@ -1,90 +1,83 @@
1
- import { SourceFile, SyntaxKind, Node, ts } from "ts-morph";
2
- import { extractEventsFromArgs } from "./extract";
1
+ import ts from "typescript";
2
+ import { resolveNodeValue } from "./resolver";
3
3
  import { resolveFunctionFromCall } from "./callResolver";
4
- import { getForwardedParamIndex } from "./flow";
5
-
6
- function getCallName(expr: Node): string {
7
- if (Node.isIdentifier(expr)) return expr.getText();
8
- if (Node.isPropertyAccessExpression(expr)) return expr.getName();
9
- return expr.getText();
10
- }
11
-
12
- function isTrackLike(name: string) {
13
- return /(track|trackFeature)/i.test(name);
14
- }
15
-
16
- function pickArg(args: Node[]) {
17
- return (
18
- args.find(Node.isStringLiteral) ??
19
- args.find(Node.isTemplateExpression) ??
20
- args.find(Node.isObjectLiteralExpression) ??
21
- args[0]
22
- );
23
- }
24
4
 
25
5
  export function scanSource(
26
- source: SourceFile,
27
- checker: ts.TypeChecker
28
- ) {
6
+ source: ts.SourceFile,
7
+ checker: ts.TypeChecker,
8
+ visited = new Set<string>(),
9
+ depth = 0
10
+ ): Set<string> {
11
+ if (depth > 5) return new Set();
29
12
  const events = new Set<string>();
30
13
 
31
- // CALLS
32
- for (const call of source.getDescendantsOfKind(SyntaxKind.CallExpression)) {
33
- const expr = call.getExpression();
34
- const name = getCallName(expr);
35
-
36
- if (!isTrackLike(name)) continue;
37
-
38
- const args = call.getArguments();
39
- if (!args.length) continue;
40
-
41
- let target = pickArg(args);
42
-
43
- const fn = resolveFunctionFromCall(expr);
44
-
45
- if (fn) {
46
- const index = getForwardedParamIndex(fn);
47
- if (index !== null && args[index]) {
48
- target = args[index];
14
+ if (visited.has(source.fileName)) return events;
15
+ visited.add(source.fileName);
16
+
17
+ function visit(node: ts.Node) {
18
+ if (ts.isCallExpression(node)) {
19
+ const name = getCallName(node.expression);
20
+
21
+ if (/(track|event|capture|send)/i.test(name)) {
22
+
23
+ const fn = resolveFunctionFromCall(node.expression, checker);
24
+
25
+ // param mapping
26
+ let paramMap = new Map<string, ts.Expression>();
27
+
28
+ if (fn && ts.isFunctionLike(fn)) {
29
+ fn.parameters.forEach((param, index) => {
30
+ const arg = node.arguments[index];
31
+ if (!arg) return;
32
+
33
+ if (ts.isIdentifier(param.name)) {
34
+ paramMap.set(param.name.text, arg);
35
+ }
36
+ });
37
+ }
38
+
39
+ // cross-file traversal
40
+ if (fn) {
41
+ const sf = fn.getSourceFile();
42
+
43
+ if (!visited.has(sf.fileName)) {
44
+ scanSource(sf, checker, visited, depth + 1)
45
+ .forEach(e => events.add(e));
46
+ }
47
+ }
48
+
49
+ for (const arg of node.arguments) {
50
+ const res = resolveNodeValue(arg, checker, paramMap);
51
+
52
+ res?.values.forEach(v => {
53
+ if (
54
+ v &&
55
+ v.length > 1 &&
56
+ v.length < 100 &&
57
+ !v.includes(" ") &&
58
+ /^[a-zA-Z0-9._:-]+$/.test(v)
59
+ ) {
60
+ events.add(v);
61
+ }
62
+ });
63
+ }
49
64
  }
50
65
  }
51
66
 
52
- extractEventsFromArgs([target], checker).forEach(({ value }) => {
53
- if (isValid(value)) events.add(value);
54
- });
67
+ ts.forEachChild(node, visit);
55
68
  }
56
69
 
57
- // JSX
58
- const jsx = [
59
- ...source.getDescendantsOfKind(SyntaxKind.JsxOpeningElement),
60
- ...source.getDescendantsOfKind(SyntaxKind.JsxSelfClosingElement),
61
- ];
62
-
63
- for (const el of jsx) {
64
- for (const attr of el.getAttributes()) {
65
- if (!Node.isJsxAttribute(attr)) continue;
66
-
67
- const name = attr.getNameNode().getText();
68
- if (!/(event|name|action)/i.test(name)) continue;
70
+ visit(source);
69
71
 
70
- const init = attr.getInitializer();
71
- if (!init) continue;
72
-
73
- const expr = Node.isJsxExpression(init)
74
- ? init.getExpression()
75
- : init;
72
+ return events;
73
+ }
76
74
 
77
- if (!expr) continue;
75
+ function getCallName(expr: ts.Expression): string {
76
+ if (ts.isIdentifier(expr)) return expr.text;
78
77
 
79
- extractEventsFromArgs([expr], checker).forEach(({ value }) => {
80
- if (isValid(value)) events.add(value);
81
- });
82
- }
78
+ if (ts.isPropertyAccessExpression(expr)) {
79
+ return expr.name.text;
83
80
  }
84
81
 
85
- return events;
86
- }
87
-
88
- function isValid(v: string) {
89
- return !!v && v.length > 1 && !v.includes(" ");
82
+ return "";
90
83
  }
package/src/types.ts CHANGED
@@ -23,3 +23,8 @@ export type EventraConfig = {
23
23
  exclude: string[];
24
24
  };
25
25
  };
26
+
27
+ export type ResolveResult = {
28
+ values: string[];
29
+ dynamic: boolean;
30
+ };
@@ -12,8 +12,7 @@ export function normalizeConfig(
12
12
  events: config.events ?? [],
13
13
  endpoint: config.endpoint ?? "",
14
14
  wrappers: config.wrappers ?? [],
15
- functionWrappers:
16
- config.functionWrappers ?? [],
15
+ functionWrappers: config.functionWrappers ?? [],
17
16
  aliases: config.aliases ?? {},
18
17
  sync: config.sync ?? {
19
18
  include: [
@@ -30,43 +29,26 @@ export function normalizeConfig(
30
29
  }
31
30
 
32
31
  export async function loadConfig(): Promise<EventraConfig | null> {
33
- const configPath = path.join(
34
- process.cwd(),
35
- CONFIG_NAME
36
- );
32
+ const configPath = path.join(process.cwd(), CONFIG_NAME);
37
33
 
38
- if (
39
- !(await fs.pathExists(
40
- configPath
41
- ))
42
- ) {
34
+ if (!(await fs.pathExists(configPath))) {
43
35
  return null;
44
36
  }
45
37
 
46
- const config =
47
- await fs.readJSON(
48
- configPath
49
- );
50
-
51
- return normalizeConfig(config);
38
+ try {
39
+ const config = await fs.readJSON(configPath);
40
+ return normalizeConfig(config);
41
+ } catch {
42
+ return null;
43
+ }
52
44
  }
53
45
 
54
- export async function saveConfig(
55
- config: EventraConfig
56
- ) {
57
- const configPath = path.join(
58
- process.cwd(),
59
- CONFIG_NAME
60
- );
46
+ export async function saveConfig(config: EventraConfig) {
47
+ const configPath = path.join(process.cwd(), CONFIG_NAME);
61
48
 
62
- const normalized =
63
- normalizeConfig(config);
49
+ const normalized = normalizeConfig(config);
64
50
 
65
- await fs.writeJSON(
66
- configPath,
67
- normalized,
68
- {
69
- spaces: 2
70
- }
71
- );
51
+ await fs.writeJSON(configPath, normalized, {
52
+ spaces: 2
53
+ });
72
54
  }
@@ -1,5 +1,12 @@
1
- export function parseAstro(
2
- content: string
3
- ) {
4
- return content;
1
+ export function parseAstro(content: string) {
2
+ const frontmatter = content.match(/---([\s\S]*?)---/);
3
+
4
+ const scripts = [
5
+ ...content.matchAll(/<script[^>]*>([\s\S]*?)<\/script>/g)
6
+ ];
7
+
8
+ return [
9
+ frontmatter?.[1] ?? "",
10
+ ...scripts.map(s => s[1])
11
+ ].join("\n");
5
12
  }
@@ -1,14 +1,9 @@
1
- export function detectParser(
2
- file: string
3
- ) {
4
- if (file.endsWith(".vue"))
5
- return "vue";
1
+ export function detectParser(file: string) {
2
+ file = file.toLowerCase();
6
3
 
7
- if (file.endsWith(".svelte"))
8
- return "svelte";
9
-
10
- if (file.endsWith(".astro"))
11
- return "astro";
4
+ if (file.endsWith(".vue")) return "vue";
5
+ if (file.endsWith(".svelte")) return "svelte";
6
+ if (file.endsWith(".astro")) return "astro";
12
7
 
13
8
  return "ts";
14
9
  }