@eventra_dev/eventra-cli 0.0.4 → 0.0.6

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 (40) hide show
  1. package/.github/workflows/release.yml +3 -0
  2. package/README.md +125 -44
  3. package/dist/commands/init.js +4 -1
  4. package/dist/commands/send.js +41 -7
  5. package/dist/commands/sync.js +35 -194
  6. package/dist/index.js +0 -0
  7. package/dist/types.js +2 -0
  8. package/dist/utils/config.js +4 -1
  9. package/dist/utils/extract.js +44 -0
  10. package/dist/utils/parsers/astro.js +6 -0
  11. package/dist/utils/parsers/router.js +12 -0
  12. package/dist/utils/parsers/svelte.js +6 -0
  13. package/dist/utils/parsers/vue.js +18 -0
  14. package/dist/utils/scanners/component-wrappers.js +55 -0
  15. package/dist/utils/scanners/function-wrappers.js +45 -0
  16. package/dist/utils/scanners/track.js +42 -0
  17. package/package.json +6 -4
  18. package/src/commands/init.ts +4 -1
  19. package/src/commands/send.ts +101 -17
  20. package/src/commands/sync.ts +66 -345
  21. package/src/types.ts +20 -0
  22. package/src/utils/config.ts +24 -7
  23. package/src/utils/extract.ts +74 -0
  24. package/src/utils/parsers/astro.ts +5 -0
  25. package/src/utils/parsers/router.ts +14 -0
  26. package/src/utils/parsers/svelte.ts +5 -0
  27. package/src/utils/parsers/vue.ts +25 -0
  28. package/src/utils/scanners/component-wrappers.ts +108 -0
  29. package/src/utils/scanners/function-wrappers.ts +98 -0
  30. package/src/utils/scanners/track.ts +84 -0
  31. package/tests/fixtures/backend/express/app.ts +78 -0
  32. package/tests/fixtures/backend/nest/service.ts +70 -0
  33. package/tests/fixtures/backend/node/index.ts +63 -0
  34. package/tests/fixtures/frontend/next/page.tsx +101 -0
  35. package/tests/fixtures/frontend/react/App.tsx +104 -0
  36. package/tests/fixtures/frontend/vue/App.vue +83 -0
  37. package/tests/fixtures/wrappers/component/test.tsx +62 -0
  38. package/tests/fixtures/wrappers/function/test.ts +60 -0
  39. package/tests/run.ts +120 -0
  40. package/tsconfig.json +2 -0
@@ -1,31 +1,39 @@
1
- import { Project, SyntaxKind } from "ts-morph";
2
- import fg from "fast-glob";
3
1
  import chalk from "chalk";
4
- import inquirer from "inquirer";
2
+ import fg from "fast-glob";
3
+ import { Project } from "ts-morph";
4
+ import fs from "fs/promises";
5
+
5
6
  import {
6
7
  loadConfig,
7
8
  saveConfig
8
9
  } from "../utils/config";
9
10
 
11
+ import { detectParser } from "../utils/parsers/router";
12
+ import { parseVue } from "../utils/parsers/vue";
13
+ import { parseSvelte } from "../utils/parsers/svelte";
14
+ import { parseAstro } from "../utils/parsers/astro";
15
+
16
+ import { scanTrack } from "../utils/scanners/track";
17
+ import { scanFunctionWrappers } from "../utils/scanners/function-wrappers";
18
+ import { scanComponentWrappers } from "../utils/scanners/component-wrappers";
19
+
10
20
  export async function sync() {
11
- let config = await loadConfig();
21
+ const config = await loadConfig();
12
22
 
13
23
  if (!config) {
14
24
  console.log(
15
- chalk.red(
16
- "Run 'eventra init' first"
17
- )
25
+ chalk.red("Run 'eventra init'")
18
26
  );
19
27
  return;
20
28
  }
21
29
 
22
- const events = new Set<string>();
23
- const project = new Project();
24
-
25
30
  console.log(
26
31
  chalk.blue("Scanning project...")
27
32
  );
28
33
 
34
+ const project = new Project();
35
+ const events = new Set<string>();
36
+
29
37
  const files = await fg(
30
38
  config.sync.include,
31
39
  {
@@ -33,365 +41,78 @@ export async function sync() {
33
41
  }
34
42
  );
35
43
 
36
- // track()
37
- for (const file of files) {
38
- const sourceFile =
39
- project.addSourceFileAtPath(file);
40
-
41
- const calls =
42
- sourceFile.getDescendantsOfKind(
43
- SyntaxKind.CallExpression
44
- );
45
-
46
- for (const call of calls) {
47
- const expression =
48
- call.getExpression();
49
-
50
- if (
51
- expression.getKind() ===
52
- SyntaxKind.PropertyAccessExpression
53
- ) {
54
- const prop =
55
- expression.asKind(
56
- SyntaxKind.PropertyAccessExpression
57
- );
58
-
59
- if (!prop) continue;
60
-
61
- if (prop.getName() !== "track")
62
- continue;
63
-
64
- const args =
65
- call.getArguments();
66
-
67
- const eventArg =
68
- args[0];
69
-
70
- if (!eventArg) continue;
71
-
72
- let event: string | null = null;
73
-
74
- // "event"
75
- if (
76
- eventArg.getKind() ===
77
- SyntaxKind.StringLiteral
78
- ) {
79
- event =
80
- eventArg
81
- .asKindOrThrow(
82
- SyntaxKind.StringLiteral
83
- )
84
- .getLiteralText();
85
- }
86
-
87
- // `event`
88
- if (
89
- eventArg.getKind() ===
90
- SyntaxKind.NoSubstitutionTemplateLiteral
91
- ) {
92
- event =
93
- eventArg
94
- .asKindOrThrow(
95
- SyntaxKind.NoSubstitutionTemplateLiteral
96
- )
97
- .getLiteralText();
98
- }
99
-
100
- if (event) {
101
- events.add(event);
102
- }
103
- }
104
- }
105
- }
106
-
107
- console.log(
108
- chalk.green(
109
- `Found ${events.size} track events`
110
- )
111
- );
112
-
113
- // wrappers setup
114
- if (!config.wrappers.length) {
115
- const { useWrapper } =
116
- await inquirer.prompt([
117
- {
118
- type: "confirm",
119
- name: "useWrapper",
120
- message:
121
- "Use wrapper components?",
122
- default: true
123
- }
124
- ]);
125
-
126
- if (useWrapper) {
127
- const wrappers = [];
128
-
129
- let addMore = true;
130
-
131
- while (addMore) {
132
- const answers =
133
- await inquirer.prompt([
134
- {
135
- type: "input",
136
- name: "name",
137
- message:
138
- "Wrapper component name:"
139
- },
140
- {
141
- type: "input",
142
- name: "prop",
143
- message:
144
- "Event prop name:"
145
- }
146
- ]);
147
-
148
- wrappers.push({
149
- name: answers.name,
150
- prop: answers.prop
151
- });
152
-
153
- const more =
154
- await inquirer.prompt([
155
- {
156
- type: "confirm",
157
- name: "more",
158
- message:
159
- "Add another wrapper?",
160
- default: false
161
- }
162
- ]);
163
-
164
- addMore = more.more;
165
- }
166
-
167
- config.wrappers = wrappers;
168
- }
169
- }
170
-
171
- // scan wrappers
172
- if (config.wrappers.length) {
173
- console.log(
174
- chalk.blue(
175
- "Scanning wrappers..."
176
- )
44
+ const functionWrappers =
45
+ (config.functionWrappers ?? []).map(
46
+ (w) => ({
47
+ ...w,
48
+ path: w.path ?? "0"
49
+ })
177
50
  );
178
51
 
179
- for (const file of files) {
180
- const sourceFile =
181
- project.addSourceFileAtPath(file);
182
-
183
- const elements = [
184
- ...sourceFile.getDescendantsOfKind(
185
- SyntaxKind.JsxOpeningElement
186
- ),
187
- ...sourceFile.getDescendantsOfKind(
188
- SyntaxKind.JsxSelfClosingElement
189
- )
190
- ];
191
-
192
- for (const element of elements) {
193
- const tagName =
194
- element
195
- .getTagNameNode()
196
- .getText()
197
- .toLowerCase();
198
-
199
- for (const wrapper of config.wrappers) {
200
- if (
201
- tagName !==
202
- wrapper.name.toLowerCase()
203
- )
204
- continue;
52
+ const componentWrappers =
53
+ config.wrappers ?? [];
205
54
 
206
- const attrs =
207
- element.getAttributes();
208
-
209
- for (const attr of attrs) {
210
- if (
211
- attr.getKind() !==
212
- SyntaxKind.JsxAttribute
213
- )
214
- continue;
215
-
216
- const attrNode =
217
- attr.asKind(
218
- SyntaxKind.JsxAttribute
219
- );
220
-
221
- if (!attrNode) continue;
222
-
223
- const attrName =
224
- attrNode
225
- .getNameNode()
226
- .getText()
227
- .toLowerCase();
228
-
229
- if (
230
- attrName !==
231
- wrapper.prop.toLowerCase()
232
- )
233
- continue;
234
-
235
- const initializer =
236
- attrNode.getInitializer();
237
-
238
- if (!initializer) continue;
239
-
240
- let value: string | null = null;
241
-
242
- // event="signup"
243
- if (
244
- initializer.getKind() ===
245
- SyntaxKind.StringLiteral
246
- ) {
247
- value =
248
- initializer
249
- .asKindOrThrow(
250
- SyntaxKind.StringLiteral
251
- )
252
- .getLiteralText();
253
- }
254
-
255
- // event={"signup"}
256
- if (
257
- initializer.getKind() ===
258
- SyntaxKind.JsxExpression
259
- ) {
260
- const expr =
261
- initializer
262
- .asKindOrThrow(
263
- SyntaxKind.JsxExpression
264
- )
265
- .getExpression();
266
-
267
- if (
268
- expr?.getKind() ===
269
- SyntaxKind.StringLiteral
270
- ) {
271
- value =
272
- expr
273
- .asKindOrThrow(
274
- SyntaxKind.StringLiteral
275
- )
276
- .getLiteralText();
277
- }
278
- }
279
-
280
- if (value) {
281
- events.add(value);
282
- }
283
- }
284
- }
285
- }
286
- }
287
- }
288
-
289
- // results
290
- const list =
291
- [...events].sort();
292
-
293
- console.log("");
294
-
295
- console.log(
296
- chalk.green("Found events:")
297
- );
298
-
299
- list.forEach((e) =>
300
- console.log(
301
- chalk.gray(`- ${e}`)
302
- )
303
- );
304
-
305
- console.log("");
55
+ for (const file of files) {
56
+ const parser =
57
+ detectParser(file);
306
58
 
307
- // diff
308
- const previous =
309
- config.events ?? [];
59
+ let content =
60
+ await fs.readFile(
61
+ file,
62
+ "utf-8"
63
+ );
310
64
 
311
- const added = list.filter(
312
- (e) => !previous.includes(e)
313
- );
65
+ if (parser === "vue")
66
+ content = parseVue(content);
314
67
 
315
- const removed =
316
- previous.filter(
317
- (e: string) =>
318
- !list.includes(e)
319
- );
68
+ if (parser === "svelte")
69
+ content = parseSvelte(content);
320
70
 
321
- if (added.length || removed.length) {
322
- console.log(
323
- chalk.blue("Changes:")
324
- );
71
+ if (parser === "astro")
72
+ content = parseAstro(content);
325
73
 
326
- if (added.length) {
327
- console.log(
328
- chalk.green(
329
- "New events:"
330
- )
331
- );
74
+ const virtualFile =
75
+ parser === "ts"
76
+ ? file
77
+ : file + ".tsx";
332
78
 
333
- added.forEach((e) =>
334
- console.log(
335
- chalk.green(
336
- `+ ${e}`
337
- )
338
- )
79
+ const source =
80
+ project.createSourceFile(
81
+ virtualFile,
82
+ content,
83
+ { overwrite: true }
339
84
  );
340
- }
341
85
 
342
- if (removed.length) {
343
- console.log(
344
- chalk.red(
345
- "Removed events:"
346
- )
347
- );
86
+ scanTrack(source).forEach(
87
+ (e) => events.add(e)
88
+ );
348
89
 
349
- removed.forEach((e: unknown) =>
350
- console.log(
351
- chalk.red(
352
- `- ${e}`
353
- )
354
- )
355
- );
356
- }
357
90
 
358
- console.log("");
359
- } else {
360
- console.log(
361
- chalk.gray(
362
- "No changes detected"
363
- )
91
+ scanFunctionWrappers(
92
+ source,
93
+ functionWrappers
94
+ ).forEach((e) =>
95
+ events.add(e)
364
96
  );
365
- }
366
-
367
- // confirm
368
- const { confirm } =
369
- await inquirer.prompt([
370
- {
371
- type: "confirm",
372
- name: "confirm",
373
- message:
374
- "Sync these events?",
375
- default: true
376
- }
377
- ]);
378
97
 
379
- if (!confirm) {
380
- console.log(
381
- chalk.yellow(
382
- "Sync cancelled"
383
- )
98
+ scanComponentWrappers(
99
+ source,
100
+ componentWrappers
101
+ ).forEach((e) =>
102
+ events.add(e)
384
103
  );
385
- return;
386
104
  }
387
105
 
106
+ const list =
107
+ [...events].sort();
108
+
388
109
  config.events = list;
389
110
 
390
111
  await saveConfig(config);
391
112
 
392
113
  console.log(
393
114
  chalk.green(
394
- "eventra.json updated"
115
+ `Found ${list.length} events`
395
116
  )
396
117
  );
397
118
  }
package/src/types.ts ADDED
@@ -0,0 +1,20 @@
1
+ export type ComponentWrapper = {
2
+ name: string;
3
+ prop: string;
4
+ };
5
+
6
+ export type FunctionWrapper = {
7
+ name: string;
8
+ path?: string;
9
+ };
10
+
11
+ export type EventraConfig = {
12
+ apiKey?: string;
13
+ events: string[];
14
+ wrappers: ComponentWrapper[];
15
+ functionWrappers: FunctionWrapper[];
16
+ sync: {
17
+ include: string[];
18
+ exclude: string[];
19
+ };
20
+ };
@@ -1,15 +1,22 @@
1
1
  import fs from "fs-extra";
2
2
  import path from "path";
3
+ import { EventraConfig } from "../types";
3
4
 
4
5
  export const CONFIG_NAME = "eventra.json";
5
6
 
6
- export function normalizeConfig(config: any) {
7
+ export function normalizeConfig(
8
+ config: Partial<EventraConfig>
9
+ ): EventraConfig {
7
10
  return {
8
11
  apiKey: config.apiKey ?? "",
9
12
  events: config.events ?? [],
10
13
  wrappers: config.wrappers ?? [],
14
+ functionWrappers:
15
+ config.functionWrappers ?? [],
11
16
  sync: config.sync ?? {
12
- include: ["**/*.{ts,tsx,js,jsx}"],
17
+ include: [
18
+ "**/*.{ts,tsx,js,jsx,vue,svelte,astro}"
19
+ ],
13
20
  exclude: [
14
21
  "node_modules",
15
22
  "dist",
@@ -20,28 +27,38 @@ export function normalizeConfig(config: any) {
20
27
  };
21
28
  }
22
29
 
23
- export async function loadConfig() {
30
+ export async function loadConfig(): Promise<EventraConfig | null> {
24
31
  const configPath = path.join(
25
32
  process.cwd(),
26
33
  CONFIG_NAME
27
34
  );
28
35
 
29
- if (!(await fs.pathExists(configPath))) {
36
+ if (
37
+ !(await fs.pathExists(
38
+ configPath
39
+ ))
40
+ ) {
30
41
  return null;
31
42
  }
32
43
 
33
- const config = await fs.readJSON(configPath);
44
+ const config =
45
+ await fs.readJSON(
46
+ configPath
47
+ );
34
48
 
35
49
  return normalizeConfig(config);
36
50
  }
37
51
 
38
- export async function saveConfig(config: any) {
52
+ export async function saveConfig(
53
+ config: EventraConfig
54
+ ) {
39
55
  const configPath = path.join(
40
56
  process.cwd(),
41
57
  CONFIG_NAME
42
58
  );
43
59
 
44
- const normalized = normalizeConfig(config);
60
+ const normalized =
61
+ normalizeConfig(config);
45
62
 
46
63
  await fs.writeJSON(
47
64
  configPath,
@@ -0,0 +1,74 @@
1
+ import {
2
+ CallExpression,
3
+ Node,
4
+ ObjectLiteralExpression,
5
+ SyntaxKind,
6
+ } from "ts-morph";
7
+
8
+ export function extractEvent(
9
+ call: CallExpression,
10
+ path: string
11
+ ): string | null {
12
+ const parts = path.split(".");
13
+
14
+ let node: Node | undefined =
15
+ call.getArguments()[Number(parts[0])];
16
+
17
+ if (!node) return null;
18
+
19
+ node = unwrap(node);
20
+
21
+ for (let i = 1; i < parts.length; i++) {
22
+ if (!Node.isObjectLiteralExpression(node)) {
23
+ return null;
24
+ }
25
+
26
+ const obj: ObjectLiteralExpression =
27
+ node;
28
+
29
+ const prop =
30
+ obj.getProperty(parts[i]);
31
+
32
+ if (!prop) return null;
33
+
34
+ if (!Node.isPropertyAssignment(prop)) {
35
+ return null;
36
+ }
37
+
38
+ const initializer =
39
+ prop.getInitializer();
40
+
41
+ if (!initializer) return null;
42
+
43
+ node = unwrap(initializer);
44
+ }
45
+
46
+ if (Node.isStringLiteral(node)) {
47
+ return node.getLiteralText();
48
+ }
49
+
50
+ if (
51
+ node.getKind() ===
52
+ SyntaxKind.NoSubstitutionTemplateLiteral
53
+ ) {
54
+ return node
55
+ .getText()
56
+ .replace(/`/g, "");
57
+ }
58
+
59
+ return null;
60
+ }
61
+
62
+ function unwrap(node: Node): Node {
63
+ let current = node;
64
+
65
+ while (
66
+ Node.isParenthesizedExpression(
67
+ current
68
+ )
69
+ ) {
70
+ current = current.getExpression();
71
+ }
72
+
73
+ return current;
74
+ }
@@ -0,0 +1,5 @@
1
+ export function parseAstro(
2
+ content: string
3
+ ) {
4
+ return content;
5
+ }
@@ -0,0 +1,14 @@
1
+ export function detectParser(
2
+ file: string
3
+ ) {
4
+ if (file.endsWith(".vue"))
5
+ return "vue";
6
+
7
+ if (file.endsWith(".svelte"))
8
+ return "svelte";
9
+
10
+ if (file.endsWith(".astro"))
11
+ return "astro";
12
+
13
+ return "ts";
14
+ }
@@ -0,0 +1,5 @@
1
+ export function parseSvelte(
2
+ content: string
3
+ ) {
4
+ return content;
5
+ }
@@ -0,0 +1,25 @@
1
+ export function parseVue(
2
+ content: string
3
+ ) {
4
+ const template =
5
+ content.match(
6
+ /<template[\s\S]*?>([\s\S]*?)<\/template>/
7
+ );
8
+
9
+ const script =
10
+ content.match(
11
+ /<script[\s\S]*?>([\s\S]*?)<\/script>/
12
+ );
13
+
14
+ return `
15
+ ${script?.[1] ?? ""}
16
+
17
+ function __vue_template__() {
18
+ return (
19
+ <>
20
+ ${template?.[1] ?? ""}
21
+ </>
22
+ );
23
+ }
24
+ `;
25
+ }