@flisk/analyze-tracking 0.2.1 → 0.2.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.
@@ -0,0 +1,17 @@
1
+ name: PR Tests
2
+
3
+ on:
4
+ pull_request_target:
5
+ branches:
6
+ - main
7
+
8
+ jobs:
9
+ test:
10
+ runs-on: ubuntu-latest
11
+ steps:
12
+ - uses: actions/checkout@v4
13
+ - uses: actions/setup-node@v4
14
+ with:
15
+ node-version: 20
16
+ - run: npm ci
17
+ - run: npm test
package/README.md CHANGED
@@ -11,7 +11,15 @@ npx @flisk/analyze-tracking /path/to/project [options]
11
11
  ```
12
12
 
13
13
  Optional arguments:
14
- - `--output <output_file>`: Name of the output file (default: `tracking-schema.yaml`)
14
+ - `-o, --output <output_file>`: Name of the output file (default: `tracking-schema.yaml`)
15
+ - `-c, --customFunction <output_file>`: Name of your custom tracking function
16
+
17
+ Note: Custom Functions only support the following format:
18
+ ```js
19
+ yourCustomTrackFunctionName('<event_name>', {
20
+ <event_parameters>
21
+ });
22
+ ```
15
23
 
16
24
 
17
25
  ## Output Schema
@@ -98,6 +106,22 @@ posthog.capture('<event_name>', {
98
106
  ```
99
107
 
100
108
 
109
+ #### Pendo
110
+ ```js
111
+ pendo.track('<event_name>', {
112
+ <event_parameters>
113
+ });
114
+ ```
115
+
116
+
117
+ #### Heap
118
+ ```js
119
+ heap.track('<event_name>', {
120
+ <event_parameters>
121
+ });
122
+ ```
123
+
124
+
101
125
  #### Snowplow
102
126
  ```js
103
127
  snowplow('trackStructEvent', {
package/bin/cli.js CHANGED
@@ -17,13 +17,18 @@ const optionDefinitions = [
17
17
  type: String,
18
18
  defaultValue: 'tracking-schema.yaml',
19
19
  },
20
+ {
21
+ name: 'customFunction',
22
+ alias: 'c',
23
+ type: String,
24
+ },
20
25
  ]
21
26
  const options = commandLineArgs(optionDefinitions);
22
- const { targetDir, output } = options;
27
+ const { targetDir, output, customFunction } = options;
23
28
 
24
29
  if (!targetDir) {
25
30
  console.error('Please provide the path to the repository.');
26
31
  process.exit(1);
27
32
  }
28
33
 
29
- run(path.resolve(targetDir), output);
34
+ run(path.resolve(targetDir), output, customFunction);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flisk/analyze-tracking",
3
- "version": "0.2.1",
3
+ "version": "0.2.3",
4
4
  "description": "Analyzes tracking code in a project and generates data schemas",
5
5
  "main": "src/index.js",
6
6
  "bin": {
package/schema.json CHANGED
@@ -64,7 +64,10 @@
64
64
  "rudderstack",
65
65
  "mparticle",
66
66
  "posthog",
67
+ "pendo",
68
+ "heap",
67
69
  "snowplow",
70
+ "custom",
68
71
  "unknown"
69
72
  ],
70
73
  "description": "Name of the platform where the event is sent"
@@ -9,14 +9,14 @@ const parser = acorn.Parser.extend(jsx());
9
9
  const parserOptions = { ecmaVersion: 'latest', sourceType: 'module', locations: true };
10
10
  extend(walk.base);
11
11
 
12
- function analyzeJsFile(filePath) {
12
+ function analyzeJsFile(filePath, customFunction) {
13
13
  const code = fs.readFileSync(filePath, 'utf8');
14
14
  const ast = parser.parse(code, parserOptions);
15
15
  const events = [];
16
16
 
17
17
  walk.ancestor(ast, {
18
18
  CallExpression(node, ancestors) {
19
- const source = detectSourceJs(node);
19
+ const source = detectSourceJs(node, customFunction);
20
20
  if (source === 'unknown') return;
21
21
 
22
22
  let eventName = null;
@@ -1,14 +1,14 @@
1
1
  const ts = require('typescript');
2
- const { detectSourceTs, findWrappingFunctionTs, extractProperties } = require('./helpers');
2
+ const { detectSourceTs, findWrappingFunctionTs, extractTsProperties } = require('./helpers');
3
3
 
4
- function analyzeTsFile(filePath, program) {
4
+ function analyzeTsFile(filePath, program, customFunction) {
5
5
  const sourceFile = program.getSourceFile(filePath);
6
6
  const checker = program.getTypeChecker();
7
7
  const events = [];
8
8
 
9
9
  function visit(node) {
10
10
  if (ts.isCallExpression(node)) {
11
- const source = detectSourceTs(node);
11
+ const source = detectSourceTs(node, customFunction);
12
12
  if (source === 'unknown') return;
13
13
 
14
14
  let eventName = null;
@@ -30,7 +30,7 @@ function analyzeTsFile(filePath, program) {
30
30
  const functionName = findWrappingFunctionTs(node);
31
31
 
32
32
  if (eventName && propertiesNode && ts.isObjectLiteralExpression(propertiesNode)) {
33
- const properties = extractProperties(checker, propertiesNode);
33
+ const properties = extractTsProperties(checker, propertiesNode);
34
34
  events.push({
35
35
  eventName,
36
36
  source,
@@ -1,11 +1,13 @@
1
1
  const ts = require('typescript');
2
2
 
3
- function detectSourceJs(node) {
3
+ function detectSourceJs(node, customFunction) {
4
4
  if (!node.callee) return 'unknown';
5
5
 
6
6
  if (node.callee.type === 'Identifier' && node.callee.name === 'gtag') {
7
7
  return 'googleanalytics';
8
- } else if (node.callee.type === 'MemberExpression') {
8
+ }
9
+
10
+ if (node.callee.type === 'MemberExpression') {
9
11
  const objectName = node.callee.object.name;
10
12
  const methodName = node.callee.property.name;
11
13
 
@@ -15,19 +17,29 @@ function detectSourceJs(node) {
15
17
  if (objectName === 'rudderanalytics' && methodName === 'track') return 'rudderstack';
16
18
  if (objectName === 'mParticle' && methodName === 'logEvent') return 'mparticle';
17
19
  if (objectName === 'posthog' && methodName === 'capture') return 'posthog';
18
- } else if (node.callee.type === 'Identifier' && node.callee.name === 'snowplow') {
20
+ if (objectName === 'pendo' && methodName === 'track') return 'pendo';
21
+ if (objectName === 'heap' && methodName === 'track') return 'heap';
22
+ }
23
+
24
+ if (node.callee.type === 'Identifier' && node.callee.name === 'snowplow') {
19
25
  return 'snowplow';
20
26
  }
21
27
 
28
+ if (node.callee.type === 'Identifier' && node.callee.name === customFunction) {
29
+ return 'custom';
30
+ }
31
+
22
32
  return 'unknown';
23
33
  }
24
34
 
25
- function detectSourceTs(node) {
35
+ function detectSourceTs(node, customFunction) {
26
36
  if (!node.expression) return 'unknown';
27
37
 
28
38
  if (ts.isIdentifier(node.expression) && node.expression.escapedText === 'gtag') {
29
39
  return 'googleanalytics';
30
- } else if (ts.isPropertyAccessExpression(node.expression)) {
40
+ }
41
+
42
+ if (ts.isPropertyAccessExpression(node.expression)) {
31
43
  const objectName = node.expression.expression.escapedText;
32
44
  const methodName = node.expression.name.escapedText;
33
45
 
@@ -37,10 +49,18 @@ function detectSourceTs(node) {
37
49
  if (objectName === 'rudderanalytics' && methodName === 'track') return 'rudderstack';
38
50
  if (objectName === 'mParticle' && methodName === 'logEvent') return 'mparticle';
39
51
  if (objectName === 'posthog' && methodName === 'capture') return 'posthog';
40
- } else if (ts.isIdentifier(node.expression) && node.expression.escapedText === 'snowplow') {
52
+ if (objectName === 'pendo' && methodName === 'track') return 'pendo';
53
+ if (objectName === 'heap' && methodName === 'track') return 'heap';
54
+ }
55
+
56
+ if (ts.isIdentifier(node.expression) && node.expression.escapedText === 'snowplow') {
41
57
  return 'snowplow';
42
58
  }
43
59
 
60
+ if (ts.isIdentifier(node.expression) && node.expression.escapedText === customFunction) {
61
+ return 'custom';
62
+ }
63
+
44
64
  return 'unknown';
45
65
  }
46
66
 
@@ -116,18 +136,24 @@ function extractJsProperties(node) {
116
136
  return properties;
117
137
  }
118
138
 
119
- function extractProperties(checker, node) {
139
+ function extractTsProperties(checker, node) {
120
140
  const properties = {};
121
141
 
122
142
  node.properties.forEach((prop) => {
123
143
  const key = prop.name ? prop.name.text : prop.key.text || prop.key.value;
124
144
  let valueType = 'any';
125
145
 
126
- if (prop.initializer) {
146
+ if (ts.isShorthandPropertyAssignment(prop)) {
147
+ const symbol = checker.getSymbolAtLocation(prop.name);
148
+ if (symbol) {
149
+ valueType = getTypeOfNode(checker, symbol.valueDeclaration);
150
+ properties[key] = { type: valueType };
151
+ }
152
+ } else if (prop.initializer) {
127
153
  if (ts.isObjectLiteralExpression(prop.initializer)) {
128
154
  properties[key] = {
129
155
  type: 'object',
130
- properties: extractProperties(checker, prop.initializer),
156
+ properties: extractTsProperties(checker, prop.initializer),
131
157
  };
132
158
  } else if (ts.isArrayLiteralExpression(prop.initializer)) {
133
159
  properties[key] = {
@@ -137,7 +163,32 @@ function extractProperties(checker, node) {
137
163
  },
138
164
  };
139
165
  } else {
140
- valueType = getTypeOfNode(checker, prop.initializer) || 'any';
166
+ // Handle hard-coded values
167
+ switch (prop.initializer.kind) {
168
+ case ts.SyntaxKind.StringLiteral:
169
+ valueType = 'string';
170
+ break;
171
+ case ts.SyntaxKind.NumericLiteral:
172
+ valueType = 'number';
173
+ break;
174
+ case ts.SyntaxKind.TrueKeyword:
175
+ case ts.SyntaxKind.FalseKeyword:
176
+ valueType = 'boolean';
177
+ break;
178
+ case ts.SyntaxKind.ArrayLiteralExpression:
179
+ valueType = 'array';
180
+ break;
181
+ case ts.SyntaxKind.ObjectLiteralExpression:
182
+ valueType = 'object';
183
+ break;
184
+ default:
185
+ valueType = 'any';
186
+ }
187
+
188
+ if (valueType === 'any') {
189
+ valueType = getTypeOfNode(checker, prop.initializer) || 'any';
190
+ }
191
+
141
192
  properties[key] = { type: valueType };
142
193
  }
143
194
  } else if (prop.type) {
@@ -160,6 +211,6 @@ module.exports = {
160
211
  findWrappingFunctionTs,
161
212
  findWrappingFunctionJs,
162
213
  extractJsProperties,
163
- extractProperties,
214
+ extractTsProperties,
164
215
  getTypeOfNode,
165
216
  };
@@ -4,7 +4,7 @@ const { getAllFiles } = require('../fileProcessor');
4
4
  const ts = require('typescript');
5
5
  const path = require('path');
6
6
 
7
- function analyzeDirectory(dirPath) {
7
+ function analyzeDirectory(dirPath, customFunction) {
8
8
  const files = getAllFiles(dirPath);
9
9
  const allEvents = {};
10
10
 
@@ -16,7 +16,7 @@ function analyzeDirectory(dirPath) {
16
16
 
17
17
  files.forEach((file) => {
18
18
  const isTsFile = /\.(tsx?)$/.test(file);
19
- const events = isTsFile ? analyzeTsFile(file, program) : analyzeJsFile(file);
19
+ const events = isTsFile ? analyzeTsFile(file, program, customFunction) : analyzeJsFile(file, customFunction);
20
20
 
21
21
  events.forEach((event) => {
22
22
  const relativeFilePath = path.relative(dirPath, event.filePath); // Calculate relative path
package/src/index.js CHANGED
@@ -2,8 +2,8 @@ const { analyzeDirectory } = require('./analyze');
2
2
  const { getRepoDetails } = require('./repoDetails');
3
3
  const { generateYamlSchema } = require('./yamlGenerator');
4
4
 
5
- function run(targetDir, outputPath) {
6
- const events = analyzeDirectory(targetDir);
5
+ function run(targetDir, outputPath, customFunction) {
6
+ const events = analyzeDirectory(targetDir, customFunction);
7
7
  const repoDetails = getRepoDetails(targetDir);
8
8
  generateYamlSchema(events, repoDetails, outputPath);
9
9
  }
@@ -1,7 +1,7 @@
1
1
  const ts = require('typescript');
2
2
  const {
3
3
  extractJsProperties,
4
- extractProperties,
4
+ extractTsProperties,
5
5
  } = require('../src/analyze/helpers');
6
6
 
7
7
  describe('extractJsProperties', () => {
@@ -69,7 +69,7 @@ describe('extractTsProperties', () => {
69
69
  getTypeAtLocation: jest.fn().mockReturnValue({}),
70
70
  typeToString: jest.fn().mockReturnValue('string'),
71
71
  };
72
- const properties = extractProperties(checker, node);
72
+ const properties = extractTsProperties(checker, node);
73
73
  expect(properties).toEqual({
74
74
  userId: { type: 'string' },
75
75
  plan: { type: 'string' },
@@ -95,7 +95,7 @@ describe('extractTsProperties', () => {
95
95
  getTypeAtLocation: jest.fn().mockReturnValue({}),
96
96
  typeToString: jest.fn().mockReturnValue('string'),
97
97
  };
98
- const properties = extractProperties(checker, node);
98
+ const properties = extractTsProperties(checker, node);
99
99
  expect(properties).toEqual({
100
100
  address: {
101
101
  type: 'object',