@eventra_dev/eventra-cli 0.0.19 → 0.1.1

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/dist/commands/check.js +17 -117
  2. package/dist/commands/sync.js +20 -115
  3. package/dist/commands/watch.js +107 -69
  4. package/dist/engine/callResolver.js +31 -0
  5. package/dist/engine/engine.js +33 -0
  6. package/dist/engine/extract.js +53 -0
  7. package/dist/engine/flow.js +38 -0
  8. package/dist/engine/graph.js +22 -0
  9. package/dist/engine/project.js +74 -0
  10. package/dist/engine/resolver.js +38 -0
  11. package/dist/engine/scanner.js +78 -0
  12. package/dist/engine/symbol.js +31 -0
  13. package/dist/utils/parsers/vue.js +16 -12
  14. package/dist/utils/processFile.js +20 -0
  15. package/package.json +1 -1
  16. package/src/commands/check.ts +19 -151
  17. package/src/commands/sync.ts +23 -194
  18. package/src/commands/watch.ts +119 -66
  19. package/src/engine/callResolver.ts +37 -0
  20. package/src/engine/engine.ts +39 -0
  21. package/src/engine/extract.ts +74 -0
  22. package/src/engine/flow.ts +54 -0
  23. package/src/engine/graph.ts +23 -0
  24. package/src/engine/project.ts +75 -0
  25. package/src/engine/resolver.ts +53 -0
  26. package/src/engine/scanner.ts +90 -0
  27. package/src/engine/symbol.ts +42 -0
  28. package/src/types.ts +3 -9
  29. package/src/utils/parsers/vue.ts +19 -20
  30. package/src/utils/processFile.ts +18 -0
  31. package/tests/fixtures/backend/express/app.ts +167 -12
  32. package/tests/fixtures/wrappers/component/test.tsx +1 -1
  33. package/tests/run.ts +4 -17
  34. package/dist/utils/detect-aliases.js +0 -39
  35. package/dist/utils/detect-wrappers.js +0 -84
  36. package/dist/utils/extract.js +0 -56
  37. package/dist/utils/scanners/component-wrappers.js +0 -73
  38. package/dist/utils/scanners/function-wrappers.js +0 -83
  39. package/dist/utils/scanners/track.js +0 -64
  40. package/src/utils/detect-aliases.ts +0 -46
  41. package/src/utils/detect-wrappers.ts +0 -119
  42. package/src/utils/extract.ts +0 -72
  43. package/src/utils/scanners/component-wrappers.ts +0 -109
  44. package/src/utils/scanners/function-wrappers.ts +0 -137
  45. package/src/utils/scanners/track.ts +0 -99
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DependencyGraph = void 0;
4
+ class DependencyGraph {
5
+ reverse = new Map();
6
+ update(file, source) {
7
+ const imports = source
8
+ .getImportDeclarations()
9
+ .map(i => i.getModuleSpecifierSourceFile()?.getFilePath())
10
+ .filter(Boolean);
11
+ for (const dep of imports) {
12
+ if (!this.reverse.has(dep)) {
13
+ this.reverse.set(dep, new Set());
14
+ }
15
+ this.reverse.get(dep).add(file);
16
+ }
17
+ }
18
+ getDependents(file) {
19
+ return [...(this.reverse.get(file) ?? [])];
20
+ }
21
+ }
22
+ exports.DependencyGraph = DependencyGraph;
@@ -0,0 +1,74 @@
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.ProjectGraph = void 0;
7
+ const ts_morph_1 = require("ts-morph");
8
+ const path_1 = __importDefault(require("path"));
9
+ const fs_1 = __importDefault(require("fs"));
10
+ function normalize(file) {
11
+ return path_1.default.resolve(file);
12
+ }
13
+ function findTsConfig(start) {
14
+ let dir = path_1.default.dirname(start);
15
+ while (true) {
16
+ const file = path_1.default.join(dir, "tsconfig.json");
17
+ if (fs_1.default.existsSync(file))
18
+ return file;
19
+ const parent = path_1.default.dirname(dir);
20
+ if (parent === dir)
21
+ break;
22
+ dir = parent;
23
+ }
24
+ return null;
25
+ }
26
+ function getScriptKind(file) {
27
+ if (file.endsWith(".tsx"))
28
+ return ts_morph_1.ts.ScriptKind.TSX;
29
+ if (file.endsWith(".jsx"))
30
+ return ts_morph_1.ts.ScriptKind.JSX;
31
+ if (file.endsWith(".js"))
32
+ return ts_morph_1.ts.ScriptKind.JS;
33
+ if (file.endsWith(".vue"))
34
+ return ts_morph_1.ts.ScriptKind.TSX;
35
+ return ts_morph_1.ts.ScriptKind.TS;
36
+ }
37
+ class ProjectGraph {
38
+ project;
39
+ constructor(entry) {
40
+ const tsconfig = findTsConfig(entry);
41
+ this.project = tsconfig
42
+ ? new ts_morph_1.Project({
43
+ tsConfigFilePath: tsconfig,
44
+ skipAddingFilesFromTsConfig: false,
45
+ })
46
+ : new ts_morph_1.Project({
47
+ useInMemoryFileSystem: true,
48
+ compilerOptions: {
49
+ allowJs: true,
50
+ jsx: ts_morph_1.ts.JsxEmit.Preserve,
51
+ moduleResolution: ts_morph_1.ts.ModuleResolutionKind.NodeJs,
52
+ target: ts_morph_1.ts.ScriptTarget.ESNext,
53
+ },
54
+ });
55
+ }
56
+ getSource(file, content) {
57
+ const filePath = normalize(file);
58
+ let source = this.project.getSourceFile(filePath);
59
+ if (!source) {
60
+ source = this.project.createSourceFile(filePath, content, {
61
+ overwrite: true,
62
+ scriptKind: getScriptKind(filePath),
63
+ });
64
+ }
65
+ else {
66
+ source.replaceWithText(content);
67
+ }
68
+ return source;
69
+ }
70
+ getChecker() {
71
+ return this.project.getTypeChecker().compilerObject;
72
+ }
73
+ }
74
+ exports.ProjectGraph = ProjectGraph;
@@ -0,0 +1,38 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.resolveNodeValue = resolveNodeValue;
4
+ const ts_morph_1 = require("ts-morph");
5
+ function resolveNodeValue(node, checker) {
6
+ if (!ts_morph_1.Node.isIdentifier(node))
7
+ return null;
8
+ const symbol = checker.getSymbolAtLocation(node.compilerNode);
9
+ if (!symbol)
10
+ return null;
11
+ const decls = symbol.getDeclarations() ?? [];
12
+ for (const d of decls) {
13
+ if (ts_morph_1.ts.isVariableDeclaration(d) && d.initializer) {
14
+ const init = d.initializer;
15
+ if (ts_morph_1.ts.isStringLiteral(init)) {
16
+ return { values: [init.text], dynamic: false };
17
+ }
18
+ if (ts_morph_1.ts.isNoSubstitutionTemplateLiteral(init)) {
19
+ return { values: [init.text], dynamic: false };
20
+ }
21
+ if (ts_morph_1.ts.isTemplateExpression(init)) {
22
+ let result = init.head.text;
23
+ for (const span of init.templateSpans) {
24
+ result += "*";
25
+ result += span.literal.text;
26
+ }
27
+ return { values: [result], dynamic: true };
28
+ }
29
+ }
30
+ if (ts_morph_1.ts.isParameter(d) && d.initializer) {
31
+ const init = d.initializer;
32
+ if (ts_morph_1.ts.isStringLiteral(init)) {
33
+ return { values: [init.text], dynamic: false };
34
+ }
35
+ }
36
+ }
37
+ return null;
38
+ }
@@ -0,0 +1,78 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.scanSource = scanSource;
4
+ const ts_morph_1 = require("ts-morph");
5
+ const extract_1 = require("./extract");
6
+ const callResolver_1 = require("./callResolver");
7
+ const flow_1 = require("./flow");
8
+ function getCallName(expr) {
9
+ if (ts_morph_1.Node.isIdentifier(expr))
10
+ return expr.getText();
11
+ if (ts_morph_1.Node.isPropertyAccessExpression(expr))
12
+ return expr.getName();
13
+ return expr.getText();
14
+ }
15
+ function isTrackLike(name) {
16
+ return /(track|trackFeature)/i.test(name);
17
+ }
18
+ function pickArg(args) {
19
+ return (args.find(ts_morph_1.Node.isStringLiteral) ??
20
+ args.find(ts_morph_1.Node.isTemplateExpression) ??
21
+ args.find(ts_morph_1.Node.isObjectLiteralExpression) ??
22
+ args[0]);
23
+ }
24
+ function scanSource(source, checker) {
25
+ const events = new Set();
26
+ // CALLS
27
+ for (const call of source.getDescendantsOfKind(ts_morph_1.SyntaxKind.CallExpression)) {
28
+ const expr = call.getExpression();
29
+ const name = getCallName(expr);
30
+ if (!isTrackLike(name))
31
+ continue;
32
+ const args = call.getArguments();
33
+ if (!args.length)
34
+ continue;
35
+ let target = pickArg(args);
36
+ const fn = (0, callResolver_1.resolveFunctionFromCall)(expr);
37
+ if (fn) {
38
+ const index = (0, flow_1.getForwardedParamIndex)(fn);
39
+ if (index !== null && args[index]) {
40
+ target = args[index];
41
+ }
42
+ }
43
+ (0, extract_1.extractEventsFromArgs)([target], checker).forEach(({ value }) => {
44
+ if (isValid(value))
45
+ events.add(value);
46
+ });
47
+ }
48
+ // JSX
49
+ const jsx = [
50
+ ...source.getDescendantsOfKind(ts_morph_1.SyntaxKind.JsxOpeningElement),
51
+ ...source.getDescendantsOfKind(ts_morph_1.SyntaxKind.JsxSelfClosingElement),
52
+ ];
53
+ for (const el of jsx) {
54
+ for (const attr of el.getAttributes()) {
55
+ if (!ts_morph_1.Node.isJsxAttribute(attr))
56
+ continue;
57
+ const name = attr.getNameNode().getText();
58
+ if (!/(event|name|action)/i.test(name))
59
+ continue;
60
+ const init = attr.getInitializer();
61
+ if (!init)
62
+ continue;
63
+ const expr = ts_morph_1.Node.isJsxExpression(init)
64
+ ? init.getExpression()
65
+ : init;
66
+ if (!expr)
67
+ continue;
68
+ (0, extract_1.extractEventsFromArgs)([expr], checker).forEach(({ value }) => {
69
+ if (isValid(value))
70
+ events.add(value);
71
+ });
72
+ }
73
+ }
74
+ return events;
75
+ }
76
+ function isValid(v) {
77
+ return !!v && v.length > 1 && !v.includes(" ");
78
+ }
@@ -0,0 +1,31 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.resolveNodeValue = resolveNodeValue;
4
+ const ts_morph_1 = require("ts-morph");
5
+ function resolveNodeValue(node, checker) {
6
+ if (ts_morph_1.Node.isIdentifier(node)) {
7
+ const symbol = checker.getSymbolAtLocation(node.compilerNode);
8
+ if (!symbol)
9
+ return null;
10
+ const decls = symbol.getDeclarations() ?? [];
11
+ for (const d of decls) {
12
+ if (ts_morph_1.ts.isVariableDeclaration(d) && d.initializer) {
13
+ if (ts_morph_1.Node.isStringLiteral(d.initializer)) {
14
+ return {
15
+ values: [d.initializer.text],
16
+ dynamic: false
17
+ };
18
+ }
19
+ }
20
+ if (ts_morph_1.ts.isParameter(d) && d.initializer) {
21
+ if (ts_morph_1.Node.isStringLiteral(d.initializer)) {
22
+ return {
23
+ values: [d.initializer.text],
24
+ dynamic: false
25
+ };
26
+ }
27
+ }
28
+ }
29
+ }
30
+ return null;
31
+ }
@@ -2,17 +2,21 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.parseVue = parseVue;
4
4
  function parseVue(content) {
5
- const template = content.match(/<template[\s\S]*?>([\s\S]*?)<\/template>/);
6
- const script = content.match(/<script[\s\S]*?>([\s\S]*?)<\/script>/);
7
- return `
8
- ${script?.[1] ?? ""}
9
-
10
- function __vue_template__() {
11
- return (
12
- <>
13
- ${template?.[1] ?? ""}
14
- </>
15
- );
16
- }
5
+ const scriptMatch = content.match(/<script[\s\S]*?>([\s\S]*?)<\/script>/);
6
+ const templateMatch = content.match(/<template>([\s\S]*?)<\/template>/);
7
+ const script = scriptMatch?.[1] ?? "";
8
+ let template = templateMatch?.[1] ?? "";
9
+ template = template.replace(/<!--[\s\S]*?-->/g, "");
10
+ template = template
11
+ .replace(/v-if="[^"]+"/g, "")
12
+ .replace(/v-for="[^"]+"/g, "")
13
+ .replace(/@click=/g, "onClick=");
14
+ const jsx = `
15
+ const __VUE_JSX__ = (
16
+ <>
17
+ ${template}
18
+ </>
19
+ );
17
20
  `;
21
+ return script + "\n" + jsx;
18
22
  }
@@ -0,0 +1,20 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.processFile = processFile;
4
+ 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
+ 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")
17
+ ? file + ".tsx"
18
+ : file;
19
+ return { content, virtualFile };
20
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eventra_dev/eventra-cli",
3
- "version": "0.0.19",
3
+ "version": "0.1.1",
4
4
  "description": "Eventra CLI",
5
5
  "type": "commonjs",
6
6
  "bin": {
@@ -1,21 +1,10 @@
1
1
  import chalk from "chalk";
2
2
  import fg from "fast-glob";
3
- import { Project } from "ts-morph";
4
3
  import fs from "fs/promises";
5
- import inquirer from "inquirer";
6
4
 
7
5
  import { loadConfig, saveConfig } from "../utils/config";
8
-
9
- import { detectParser } from "../utils/parsers/router";
10
- import { parseVue } from "../utils/parsers/vue";
11
- import { parseSvelte } from "../utils/parsers/svelte";
12
- import { parseAstro } from "../utils/parsers/astro";
13
-
14
- import { scanTrack } from "../utils/scanners/track";
15
- import { scanFunctionWrappers } from "../utils/scanners/function-wrappers";
16
- import { scanComponentWrappers } from "../utils/scanners/component-wrappers";
17
-
18
- import { ExtractedEvent } from "../types";
6
+ import { EventraEngine } from "../engine/engine";
7
+ import { processFile } from "../utils/processFile";
19
8
 
20
9
  export async function check({ fix = false }: { fix?: boolean }) {
21
10
  const config = await loadConfig();
@@ -27,169 +16,48 @@ export async function check({ fix = false }: { fix?: boolean }) {
27
16
 
28
17
  console.log(chalk.blue("Checking events..."));
29
18
 
30
- const project = new Project();
31
-
32
- const knownEvents = new Set(config.events ?? []);
33
- const aliases = { ...(config.aliases ?? {}) };
34
-
35
- const foundEvents = new Map<string, ExtractedEvent>();
36
- const newEvents = new Map<string, ExtractedEvent>();
37
- const unresolvedDynamic = new Map<string, ExtractedEvent>();
38
-
39
19
  const files = await fg(config.sync.include, {
40
- ignore: config.sync.exclude
20
+ ignore: config.sync.exclude,
41
21
  });
42
22
 
23
+ const engine = new EventraEngine(process.cwd());
24
+
43
25
  for (const file of files) {
44
26
  try {
45
- const stat = await fs.stat(file);
46
-
47
- if (stat.size > 2 * 1024 * 1024) {
48
- continue;
49
- }
50
-
51
- const parser = detectParser(file);
52
- let content = await fs.readFile(file, "utf-8");
53
-
54
- if (parser === "vue") content = parseVue(content);
55
- if (parser === "svelte") content = parseSvelte(content);
56
- if (parser === "astro") content = parseAstro(content);
57
-
58
- const source = project.createSourceFile(file, content, {
59
- overwrite: true
60
- });
61
-
62
- const found: ExtractedEvent[] = [
63
- ...scanTrack(source, config, aliases),
64
- ...scanFunctionWrappers(source, config.functionWrappers ?? [], aliases),
65
- ...scanComponentWrappers(source, config.wrappers ?? [], aliases)
66
- ];
67
-
68
- for (const event of found) {
69
- const { value, dynamic } = event;
70
- if (!value) continue;
71
-
72
- // skip
73
- if (aliases[value] === "__skip__") continue;
74
-
75
- // alias resolve
76
- if (value in aliases) {
77
- foundEvents.set(aliases[value], {
78
- ...event,
79
- value: aliases[value]
80
- });
81
- continue;
82
- }
83
-
84
- if (dynamic) {
85
- unresolvedDynamic.set(value, event);
86
- continue;
87
- }
27
+ const raw = await fs.readFile(file, "utf-8");
88
28
 
89
- foundEvents.set(value, event);
90
- }
29
+ const { content, virtualFile } = processFile(file, raw);
91
30
 
92
- project.removeSourceFile(source);
31
+ engine.scanFile(virtualFile, content);
93
32
 
94
33
  } catch {
95
34
  console.log(chalk.gray(`Skipped: ${file}`));
96
35
  }
97
36
  }
98
37
 
99
- // find new
100
- for (const [key, event] of foundEvents) {
101
- if (!knownEvents.has(key)) {
102
- newEvents.set(key, event);
103
- }
104
- }
105
-
106
- // FIX MODE
107
- if (fix) {
108
- console.log(chalk.yellow("\nFix mode\n"));
38
+ const found = new Set(engine.getAllEvents());
39
+ const known = new Set(config.events ?? []);
109
40
 
110
- // add new events
111
- for (const [name] of newEvents) {
112
- config.events.push(name);
113
- console.log(chalk.green(`+ ${name}`));
114
- }
115
-
116
- // resolve dynamic
117
- for (const [value] of unresolvedDynamic) {
118
- const { action } = await inquirer.prompt([
119
- {
120
- type: "list",
121
- name: "action",
122
- message: `Dynamic: ${value}`,
123
- choices: [
124
- { name: "Alias", value: "alias" },
125
- { name: "Skip", value: "skip" }
126
- ]
127
- }
128
- ]);
129
-
130
- if (action === "skip") {
131
- aliases[value] = "__skip__";
132
- continue;
133
- }
134
-
135
- const { name } = await inquirer.prompt([
136
- {
137
- type: "input",
138
- name: "name",
139
- message: "Event name:"
140
- }
141
- ]);
142
-
143
- const normalized = name.trim().toLowerCase();
144
- if (!normalized) continue;
145
-
146
- aliases[value] = normalized;
147
- config.events.push(normalized);
148
-
149
- console.log(chalk.green(`${value} → ${normalized}`));
150
- }
41
+ const newEvents = [...found].filter(e => !known.has(e));
151
42
 
152
- config.aliases = Object.fromEntries(
153
- Object.entries(aliases).sort()
154
- );
43
+ if (fix) {
44
+ newEvents.forEach(e => {
45
+ config.events.push(e);
46
+ console.log(chalk.green(`+ ${e}`));
47
+ });
155
48
 
156
49
  config.events = [...new Set(config.events)].sort();
157
50
 
158
51
  await saveConfig(config);
159
52
 
160
- console.log(chalk.green("\n✔ Fixed"));
53
+ console.log(chalk.green("\nFixed"));
161
54
  return;
162
55
  }
163
56
 
164
- // NORMAL MODE
165
- let hasError = false;
166
-
167
- if (unresolvedDynamic.size) {
168
- hasError = true;
169
-
170
- console.log(chalk.red("\nDynamic events:"));
171
-
172
- unresolvedDynamic.forEach((e) =>
173
- console.log(
174
- chalk.red(`- ${e.value} (${e.file}:${e.line})`)
175
- )
176
- );
177
- }
178
-
179
- if (newEvents.size) {
180
- hasError = true;
181
-
57
+ if (newEvents.length) {
182
58
  console.log(chalk.red("\nNew events:"));
59
+ newEvents.forEach(e => console.log(chalk.red(`+ ${e}`)));
183
60
 
184
- newEvents.forEach((e) =>
185
- console.log(
186
- chalk.red(`+ ${e.value} (${e.file}:${e.line})`)
187
- )
188
- );
189
- }
190
-
191
- if (hasError) {
192
- console.log(chalk.red("\nEvent check failed"));
193
61
  process.exit(1);
194
62
  }
195
63