@eventra_dev/eventra-cli 0.1.2 → 0.1.4
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.
- package/README.md +48 -73
- package/dist/commands/check.js +18 -7
- package/dist/commands/send.js +7 -1
- package/dist/commands/sync.js +5 -3
- package/dist/commands/watch.js +13 -55
- package/dist/engine/callResolver.js +9 -21
- package/dist/engine/engine.js +12 -1
- package/dist/engine/functionAnalyzer.js +45 -0
- package/dist/engine/graph.js +75 -7
- package/dist/engine/languageService.js +24 -18
- package/dist/engine/resolver.js +37 -4
- package/dist/engine/scanner.js +67 -17
- package/dist/utils/config.js +7 -2
- package/dist/utils/parsers/astro.js +8 -1
- package/dist/utils/parsers/router.js +1 -0
- package/dist/utils/parsers/svelte.js +4 -1
- package/dist/utils/parsers/vue.js +13 -4
- package/dist/utils/processFile.js +3 -1
- package/package.json +1 -1
- package/src/commands/check.ts +21 -7
- package/src/commands/send.ts +8 -2
- package/src/commands/sync.ts +6 -3
- package/src/commands/watch.ts +14 -60
- package/src/engine/callResolver.ts +9 -26
- package/src/engine/engine.ts +15 -3
- package/src/engine/functionAnalyzer.ts +51 -0
- package/src/engine/graph.ts +93 -6
- package/src/engine/languageService.ts +47 -20
- package/src/engine/resolver.ts +51 -4
- package/src/engine/scanner.ts +88 -21
- package/src/utils/config.ts +15 -33
- package/src/utils/parsers/astro.ts +11 -4
- package/src/utils/parsers/router.ts +5 -10
- package/src/utils/parsers/svelte.ts +6 -4
- package/src/utils/parsers/vue.ts +15 -5
- package/src/utils/processFile.ts +6 -3
package/src/engine/graph.ts
CHANGED
|
@@ -1,23 +1,110 @@
|
|
|
1
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>>();
|
|
7
|
+
private exports = new Map<string, Set<string>>();
|
|
8
|
+
|
|
9
|
+
private normalize(file: string) {
|
|
10
|
+
return path.resolve(file);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
private resolveImport(from: string, module: string): string | null {
|
|
14
|
+
if (!module.startsWith(".")) return null;
|
|
15
|
+
|
|
16
|
+
const base = path.resolve(path.dirname(from), module);
|
|
17
|
+
|
|
18
|
+
const tryFiles = [
|
|
19
|
+
base,
|
|
20
|
+
base + ".ts",
|
|
21
|
+
base + ".tsx",
|
|
22
|
+
base + ".js",
|
|
23
|
+
base + ".jsx",
|
|
24
|
+
path.join(base, "index.ts"),
|
|
25
|
+
path.join(base, "index.tsx"),
|
|
26
|
+
path.join(base, "index.js"),
|
|
27
|
+
];
|
|
28
|
+
|
|
29
|
+
for (const f of tryFiles) {
|
|
30
|
+
if (fs.existsSync(f)) {
|
|
31
|
+
return this.normalize(f);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return this.normalize(base);
|
|
36
|
+
}
|
|
5
37
|
|
|
6
38
|
update(file: string, source: ts.SourceFile) {
|
|
39
|
+
const normalizedFile = this.normalize(file);
|
|
40
|
+
|
|
41
|
+
this.exports.set(normalizedFile, new Set());
|
|
42
|
+
|
|
7
43
|
for (const stmt of source.statements) {
|
|
8
|
-
|
|
44
|
+
// IMPORT
|
|
45
|
+
if (ts.isImportDeclaration(stmt)) {
|
|
46
|
+
const module = stmt.moduleSpecifier.getText().replace(/['"]/g, "");
|
|
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);
|
|
56
|
+
}
|
|
9
57
|
|
|
10
|
-
|
|
58
|
+
// EXPORT
|
|
59
|
+
if (ts.isExportDeclaration(stmt) && stmt.moduleSpecifier) {
|
|
60
|
+
const module = stmt.moduleSpecifier.getText().replace(/['"]/g, "");
|
|
61
|
+
const resolved = this.resolveImport(normalizedFile, module);
|
|
11
62
|
|
|
12
|
-
|
|
13
|
-
|
|
63
|
+
if (!resolved) continue;
|
|
64
|
+
|
|
65
|
+
if (!this.exports.has(normalizedFile)) {
|
|
66
|
+
this.exports.set(normalizedFile, new Set());
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
this.exports.get(normalizedFile)!.add(resolved);
|
|
70
|
+
|
|
71
|
+
if (!this.reverse.has(resolved)) {
|
|
72
|
+
this.reverse.set(resolved, new Set());
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
this.reverse.get(resolved)!.add(normalizedFile);
|
|
14
76
|
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
getAllRelated(file: string, visited = new Set<string>()): Set<string> {
|
|
81
|
+
const normalized = this.normalize(file);
|
|
82
|
+
|
|
83
|
+
if (visited.has(normalized)) return visited;
|
|
84
|
+
visited.add(normalized);
|
|
15
85
|
|
|
16
|
-
|
|
86
|
+
const deps = this.exports.get(normalized);
|
|
87
|
+
|
|
88
|
+
if (!deps) return visited;
|
|
89
|
+
|
|
90
|
+
for (const dep of deps) {
|
|
91
|
+
this.getAllRelated(dep, visited);
|
|
17
92
|
}
|
|
93
|
+
|
|
94
|
+
return visited;
|
|
18
95
|
}
|
|
19
96
|
|
|
20
97
|
getDependents(file: string) {
|
|
21
|
-
return [...(this.reverse.get(file) ?? [])];
|
|
98
|
+
return [...(this.reverse.get(this.normalize(file)) ?? [])];
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
remove(file: string) {
|
|
102
|
+
const normalized = this.normalize(file);
|
|
103
|
+
|
|
104
|
+
this.exports.delete(normalized);
|
|
105
|
+
|
|
106
|
+
for (const set of this.reverse.values()) {
|
|
107
|
+
set.delete(normalized);
|
|
108
|
+
}
|
|
22
109
|
}
|
|
23
110
|
}
|
|
@@ -3,13 +3,49 @@ import fs from "fs";
|
|
|
3
3
|
|
|
4
4
|
export class TSService {
|
|
5
5
|
private files = new Map<string, { version: number; content: string }>();
|
|
6
|
+
private fileNames = new Set<string>();
|
|
6
7
|
private service: ts.LanguageService;
|
|
7
8
|
|
|
8
9
|
constructor(private root: string) {
|
|
10
|
+
const configPath = ts.findConfigFile(
|
|
11
|
+
root,
|
|
12
|
+
ts.sys.fileExists,
|
|
13
|
+
"tsconfig.json"
|
|
14
|
+
);
|
|
15
|
+
|
|
16
|
+
let compilerOptions: ts.CompilerOptions = {
|
|
17
|
+
allowJs: true,
|
|
18
|
+
jsx: ts.JsxEmit.Preserve,
|
|
19
|
+
moduleResolution: ts.ModuleResolutionKind.Node16,
|
|
20
|
+
target: ts.ScriptTarget.ESNext,
|
|
21
|
+
esModuleInterop: true,
|
|
22
|
+
allowSyntheticDefaultImports: true,
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
if (configPath) {
|
|
26
|
+
const configFile = ts.readConfigFile(
|
|
27
|
+
configPath,
|
|
28
|
+
ts.sys.readFile
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
const parsed = ts.parseJsonConfigFileContent(
|
|
32
|
+
configFile.config,
|
|
33
|
+
ts.sys,
|
|
34
|
+
root
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
compilerOptions = {
|
|
38
|
+
...compilerOptions,
|
|
39
|
+
...parsed.options,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
9
43
|
const host: ts.LanguageServiceHost = {
|
|
10
|
-
getScriptFileNames: () => [...this.
|
|
44
|
+
getScriptFileNames: () => [...this.fileNames],
|
|
45
|
+
|
|
11
46
|
getScriptVersion: (file) =>
|
|
12
47
|
this.files.get(file)?.version.toString() ?? "0",
|
|
48
|
+
|
|
13
49
|
getScriptSnapshot: (file) => {
|
|
14
50
|
const f = this.files.get(file);
|
|
15
51
|
|
|
@@ -19,32 +55,21 @@ export class TSService {
|
|
|
19
55
|
|
|
20
56
|
if (fs.existsSync(file)) {
|
|
21
57
|
return ts.ScriptSnapshot.fromString(
|
|
22
|
-
fs.readFileSync(file, "utf-8"
|
|
58
|
+
fs.readFileSync(file, "utf-8")
|
|
23
59
|
);
|
|
24
60
|
}
|
|
25
61
|
|
|
26
62
|
return undefined;
|
|
27
63
|
},
|
|
64
|
+
|
|
28
65
|
getCurrentDirectory: () => this.root,
|
|
29
|
-
getCompilationSettings: () =>
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
moduleResolution: ts.ModuleResolutionKind.Node16,
|
|
33
|
-
target: ts.ScriptTarget.ESNext,
|
|
34
|
-
esModuleInterop: true,
|
|
35
|
-
allowSyntheticDefaultImports: true,
|
|
36
|
-
}),
|
|
37
|
-
getDefaultLibFileName: (opts) => ts.getDefaultLibFilePath(opts),
|
|
38
|
-
|
|
39
|
-
readFile: (fileName: string, encoding?: string) => {
|
|
40
|
-
try {
|
|
41
|
-
return fs.readFileSync(fileName, encoding as BufferEncoding);
|
|
42
|
-
} catch (err) {
|
|
43
|
-
return undefined;
|
|
44
|
-
}
|
|
45
|
-
},
|
|
66
|
+
getCompilationSettings: () => compilerOptions,
|
|
67
|
+
getDefaultLibFileName: (opts) =>
|
|
68
|
+
ts.getDefaultLibFilePath(opts),
|
|
46
69
|
|
|
47
|
-
|
|
70
|
+
readFile: ts.sys.readFile,
|
|
71
|
+
fileExists: ts.sys.fileExists,
|
|
72
|
+
readDirectory: ts.sys.readDirectory,
|
|
48
73
|
};
|
|
49
74
|
|
|
50
75
|
this.service = ts.createLanguageService(host);
|
|
@@ -57,6 +82,8 @@ export class TSService {
|
|
|
57
82
|
content,
|
|
58
83
|
version: prev ? prev.version + 1 : 1,
|
|
59
84
|
});
|
|
85
|
+
|
|
86
|
+
this.fileNames.add(file);
|
|
60
87
|
}
|
|
61
88
|
|
|
62
89
|
getProgram() {
|
package/src/engine/resolver.ts
CHANGED
|
@@ -7,9 +7,23 @@ export type ResolveResult = {
|
|
|
7
7
|
|
|
8
8
|
export function resolveNodeValue(
|
|
9
9
|
node: ts.Node,
|
|
10
|
-
checker: ts.TypeChecker
|
|
10
|
+
checker: ts.TypeChecker,
|
|
11
|
+
paramMap?: Map<string, ts.Expression>,
|
|
12
|
+
seen = new Set<ts.Node>()
|
|
11
13
|
): ResolveResult | null {
|
|
12
14
|
|
|
15
|
+
if (seen.has(node)) return null;
|
|
16
|
+
seen.add(node);
|
|
17
|
+
|
|
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
|
+
|
|
13
27
|
if (ts.isStringLiteral(node)) {
|
|
14
28
|
return { values: [node.text], dynamic: false };
|
|
15
29
|
}
|
|
@@ -26,8 +40,8 @@ export function resolveNodeValue(
|
|
|
26
40
|
}
|
|
27
41
|
|
|
28
42
|
if (ts.isConditionalExpression(node)) {
|
|
29
|
-
const a = resolveNodeValue(node.whenTrue, checker);
|
|
30
|
-
const b = resolveNodeValue(node.whenFalse, checker);
|
|
43
|
+
const a = resolveNodeValue(node.whenTrue, checker, paramMap, seen);
|
|
44
|
+
const b = resolveNodeValue(node.whenFalse, checker, paramMap, seen);
|
|
31
45
|
|
|
32
46
|
return {
|
|
33
47
|
values: [...new Set([...(a?.values ?? []), ...(b?.values ?? [])])],
|
|
@@ -45,7 +59,7 @@ export function resolveNodeValue(
|
|
|
45
59
|
|
|
46
60
|
for (const decl of symbol.getDeclarations() ?? []) {
|
|
47
61
|
if (ts.isVariableDeclaration(decl) && decl.initializer) {
|
|
48
|
-
return resolveNodeValue(decl.initializer, checker);
|
|
62
|
+
return resolveNodeValue(decl.initializer, checker, paramMap, seen);
|
|
49
63
|
}
|
|
50
64
|
|
|
51
65
|
if (ts.isEnumMember(decl) && decl.initializer) {
|
|
@@ -82,5 +96,38 @@ export function resolveNodeValue(
|
|
|
82
96
|
}
|
|
83
97
|
}
|
|
84
98
|
|
|
99
|
+
if (ts.isObjectLiteralExpression(node)) {
|
|
100
|
+
const values: string[] = [];
|
|
101
|
+
|
|
102
|
+
for (const prop of node.properties) {
|
|
103
|
+
if (!ts.isPropertyAssignment(prop)) continue;
|
|
104
|
+
|
|
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);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
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);
|
|
120
|
+
|
|
121
|
+
if (left && right) {
|
|
122
|
+
return {
|
|
123
|
+
values: left.values.flatMap(l =>
|
|
124
|
+
right.values.map(r => l + r)
|
|
125
|
+
),
|
|
126
|
+
dynamic: true,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
85
132
|
return null;
|
|
86
133
|
}
|
package/src/engine/scanner.ts
CHANGED
|
@@ -1,12 +1,52 @@
|
|
|
1
1
|
import ts from "typescript";
|
|
2
2
|
import { resolveNodeValue } from "./resolver";
|
|
3
3
|
import { resolveFunctionFromCall } from "./callResolver";
|
|
4
|
+
import { findTrackParamIndex } from "./functionAnalyzer";
|
|
5
|
+
|
|
6
|
+
function getCallName(expr: ts.Expression): string {
|
|
7
|
+
if (ts.isIdentifier(expr)) return expr.text;
|
|
8
|
+
|
|
9
|
+
if (ts.isPropertyAccessExpression(expr)) {
|
|
10
|
+
return expr.name.text;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
return "";
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function pickBestArg(
|
|
17
|
+
args: readonly ts.Expression[],
|
|
18
|
+
checker: ts.TypeChecker,
|
|
19
|
+
paramMap: Map<string, ts.Expression>
|
|
20
|
+
) {
|
|
21
|
+
let best: { score: number; node: ts.Expression } | null = null;
|
|
22
|
+
|
|
23
|
+
for (const arg of args) {
|
|
24
|
+
const res = resolveNodeValue(arg, checker, paramMap);
|
|
25
|
+
|
|
26
|
+
if (!res?.values?.length) continue;
|
|
27
|
+
|
|
28
|
+
let score = 0;
|
|
29
|
+
|
|
30
|
+
if (!res.dynamic) score += 3;
|
|
31
|
+
if (res.values.every(v => v.length < 50)) score += 2;
|
|
32
|
+
if (res.values.some(v => v.includes("*"))) score -= 2;
|
|
33
|
+
|
|
34
|
+
if (!best || score > best.score) {
|
|
35
|
+
best = { score, node: arg };
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return best?.node;
|
|
40
|
+
}
|
|
4
41
|
|
|
5
42
|
export function scanSource(
|
|
6
43
|
source: ts.SourceFile,
|
|
7
44
|
checker: ts.TypeChecker,
|
|
8
|
-
visited = new Set<string>()
|
|
9
|
-
|
|
45
|
+
visited = new Set<string>(),
|
|
46
|
+
depth = 0
|
|
47
|
+
): Set<string> {
|
|
48
|
+
if (depth > 5) return new Set();
|
|
49
|
+
|
|
10
50
|
const events = new Set<string>();
|
|
11
51
|
|
|
12
52
|
if (visited.has(source.fileName)) return events;
|
|
@@ -17,24 +57,61 @@ export function scanSource(
|
|
|
17
57
|
const name = getCallName(node.expression);
|
|
18
58
|
|
|
19
59
|
if (/(track|event|capture|send)/i.test(name)) {
|
|
20
|
-
let target = node.arguments[0];
|
|
21
|
-
|
|
22
60
|
const fn = resolveFunctionFromCall(node.expression, checker);
|
|
23
61
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
62
|
+
const paramMap = new Map<string, ts.Expression>();
|
|
63
|
+
|
|
64
|
+
if (fn && ts.isFunctionLike(fn)) {
|
|
65
|
+
fn.parameters.forEach((param, index) => {
|
|
66
|
+
const arg = node.arguments[index];
|
|
67
|
+
if (!arg) return;
|
|
68
|
+
|
|
69
|
+
if (ts.isIdentifier(param.name)) {
|
|
70
|
+
paramMap.set(param.name.text, arg);
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (fn && ts.isFunctionLike(fn)) {
|
|
76
|
+
const idx = findTrackParamIndex(fn);
|
|
77
|
+
|
|
78
|
+
if (idx !== null) {
|
|
79
|
+
const realArg = node.arguments[idx];
|
|
27
80
|
|
|
28
|
-
|
|
29
|
-
|
|
81
|
+
if (realArg) {
|
|
82
|
+
const res = resolveNodeValue(realArg, checker, paramMap);
|
|
83
|
+
|
|
84
|
+
res?.values.forEach(v => {
|
|
85
|
+
if (
|
|
86
|
+
v &&
|
|
87
|
+
v.length > 1 &&
|
|
88
|
+
v.length < 100 &&
|
|
89
|
+
!v.includes(" ") &&
|
|
90
|
+
/^[a-zA-Z0-9._:-]+$/.test(v)
|
|
91
|
+
) {
|
|
92
|
+
events.add(v);
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
30
98
|
}
|
|
31
99
|
}
|
|
32
100
|
|
|
101
|
+
// fallback (heuristic)
|
|
102
|
+
const target = pickBestArg(node.arguments, checker, paramMap);
|
|
103
|
+
|
|
33
104
|
if (target) {
|
|
34
|
-
const res = resolveNodeValue(target, checker);
|
|
105
|
+
const res = resolveNodeValue(target, checker, paramMap);
|
|
35
106
|
|
|
36
107
|
res?.values.forEach(v => {
|
|
37
|
-
if (
|
|
108
|
+
if (
|
|
109
|
+
v &&
|
|
110
|
+
v.length > 1 &&
|
|
111
|
+
v.length < 100 &&
|
|
112
|
+
!v.includes(" ") &&
|
|
113
|
+
/^[a-zA-Z0-9._:-]+$/.test(v)
|
|
114
|
+
) {
|
|
38
115
|
events.add(v);
|
|
39
116
|
}
|
|
40
117
|
});
|
|
@@ -49,13 +126,3 @@ export function scanSource(
|
|
|
49
126
|
|
|
50
127
|
return events;
|
|
51
128
|
}
|
|
52
|
-
|
|
53
|
-
function getCallName(expr: ts.Expression): string {
|
|
54
|
-
if (ts.isIdentifier(expr)) return expr.text;
|
|
55
|
-
|
|
56
|
-
if (ts.isPropertyAccessExpression(expr)) {
|
|
57
|
-
return expr.name.text;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
return "";
|
|
61
|
-
}
|
package/src/utils/config.ts
CHANGED
|
@@ -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
|
-
|
|
47
|
-
await fs.readJSON(
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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
|
-
|
|
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
|
-
|
|
67
|
-
|
|
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
|
|
3
|
-
|
|
4
|
-
|
|
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
|
|
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(".
|
|
8
|
-
|
|
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
|
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
export function parseSvelte(
|
|
2
|
-
|
|
3
|
-
)
|
|
4
|
-
|
|
1
|
+
export function parseSvelte(content: string) {
|
|
2
|
+
const scriptMatches = [
|
|
3
|
+
...content.matchAll(/<script[^>]*>([\s\S]*?)<\/script>/g)
|
|
4
|
+
];
|
|
5
|
+
|
|
6
|
+
return scriptMatches.map(m => m[1]).join("\n");
|
|
5
7
|
}
|
package/src/utils/parsers/vue.ts
CHANGED
|
@@ -1,16 +1,26 @@
|
|
|
1
1
|
export function parseVue(content: string) {
|
|
2
|
-
const
|
|
3
|
-
|
|
2
|
+
const scriptMatches = [
|
|
3
|
+
...content.matchAll(/<script[^>]*>([\s\S]*?)<\/script>/g)
|
|
4
|
+
];
|
|
5
|
+
|
|
6
|
+
const script = scriptMatches.map(m => m[1]).join("\n");
|
|
4
7
|
|
|
5
|
-
|
|
8
|
+
// template
|
|
9
|
+
const templateMatch = content.match(/<template>([\s\S]*?)<\/template>/);
|
|
6
10
|
let template = templateMatch?.[1] ?? "";
|
|
7
11
|
|
|
8
12
|
template = template.replace(/<!--[\s\S]*?-->/g, "");
|
|
9
13
|
|
|
14
|
+
// Vue → JSX
|
|
10
15
|
template = template
|
|
16
|
+
.replace(/\bclass=/g, "className=")
|
|
17
|
+
.replace(/\bfor=/g, "htmlFor=")
|
|
18
|
+
.replace(/@(\w+)=/g, (_, e) => {
|
|
19
|
+
const name = e.charAt(0).toUpperCase() + e.slice(1);
|
|
20
|
+
return `on${name}=`;
|
|
21
|
+
})
|
|
11
22
|
.replace(/v-if="[^"]+"/g, "")
|
|
12
|
-
.replace(/v-for="[^"]+"/g, "")
|
|
13
|
-
.replace(/@click=/g, "onClick=");
|
|
23
|
+
.replace(/v-for="[^"]+"/g, "");
|
|
14
24
|
|
|
15
25
|
const jsx = `
|
|
16
26
|
const __VUE_JSX__ = (
|
package/src/utils/processFile.ts
CHANGED
|
@@ -10,9 +10,12 @@ export function processFile(file: string, content: string) {
|
|
|
10
10
|
if (parser === "svelte") content = parseSvelte(content);
|
|
11
11
|
if (parser === "astro") content = parseAstro(content);
|
|
12
12
|
|
|
13
|
-
const virtualFile =
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
const virtualFile =
|
|
14
|
+
file.endsWith(".vue") ||
|
|
15
|
+
file.endsWith(".svelte") ||
|
|
16
|
+
file.endsWith(".astro")
|
|
17
|
+
? file + ".tsx"
|
|
18
|
+
: file;
|
|
16
19
|
|
|
17
20
|
return { content, virtualFile };
|
|
18
21
|
}
|