@flisk/analyze-tracking 0.2.2 → 0.2.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 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
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.2",
3
+ "version": "0.2.4",
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
@@ -67,6 +67,7 @@
67
67
  "pendo",
68
68
  "heap",
69
69
  "snowplow",
70
+ "custom",
70
71
  "unknown"
71
72
  ],
72
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
 
@@ -17,19 +19,27 @@ function detectSourceJs(node) {
17
19
  if (objectName === 'posthog' && methodName === 'capture') return 'posthog';
18
20
  if (objectName === 'pendo' && methodName === 'track') return 'pendo';
19
21
  if (objectName === 'heap' && methodName === 'track') return 'heap';
20
- } else if (node.callee.type === 'Identifier' && node.callee.name === 'snowplow') {
22
+ }
23
+
24
+ if (node.callee.type === 'Identifier' && node.callee.name === 'snowplow') {
21
25
  return 'snowplow';
22
26
  }
23
27
 
28
+ if (node.callee.type === 'Identifier' && node.callee.name === customFunction) {
29
+ return 'custom';
30
+ }
31
+
24
32
  return 'unknown';
25
33
  }
26
34
 
27
- function detectSourceTs(node) {
35
+ function detectSourceTs(node, customFunction) {
28
36
  if (!node.expression) return 'unknown';
29
37
 
30
38
  if (ts.isIdentifier(node.expression) && node.expression.escapedText === 'gtag') {
31
39
  return 'googleanalytics';
32
- } else if (ts.isPropertyAccessExpression(node.expression)) {
40
+ }
41
+
42
+ if (ts.isPropertyAccessExpression(node.expression)) {
33
43
  const objectName = node.expression.expression.escapedText;
34
44
  const methodName = node.expression.name.escapedText;
35
45
 
@@ -41,10 +51,16 @@ function detectSourceTs(node) {
41
51
  if (objectName === 'posthog' && methodName === 'capture') return 'posthog';
42
52
  if (objectName === 'pendo' && methodName === 'track') return 'pendo';
43
53
  if (objectName === 'heap' && methodName === 'track') return 'heap';
44
- } else if (ts.isIdentifier(node.expression) && node.expression.escapedText === 'snowplow') {
54
+ }
55
+
56
+ if (ts.isIdentifier(node.expression) && node.expression.escapedText === 'snowplow') {
45
57
  return 'snowplow';
46
58
  }
47
59
 
60
+ if (ts.isIdentifier(node.expression) && node.expression.escapedText === customFunction) {
61
+ return 'custom';
62
+ }
63
+
48
64
  return 'unknown';
49
65
  }
50
66
 
@@ -120,18 +136,24 @@ function extractJsProperties(node) {
120
136
  return properties;
121
137
  }
122
138
 
123
- function extractProperties(checker, node) {
139
+ function extractTsProperties(checker, node) {
124
140
  const properties = {};
125
141
 
126
142
  node.properties.forEach((prop) => {
127
143
  const key = prop.name ? prop.name.text : prop.key.text || prop.key.value;
128
144
  let valueType = 'any';
129
145
 
130
- 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) {
131
153
  if (ts.isObjectLiteralExpression(prop.initializer)) {
132
154
  properties[key] = {
133
155
  type: 'object',
134
- properties: extractProperties(checker, prop.initializer),
156
+ properties: extractTsProperties(checker, prop.initializer),
135
157
  };
136
158
  } else if (ts.isArrayLiteralExpression(prop.initializer)) {
137
159
  properties[key] = {
@@ -141,7 +163,32 @@ function extractProperties(checker, node) {
141
163
  },
142
164
  };
143
165
  } else {
144
- 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
+
145
192
  properties[key] = { type: valueType };
146
193
  }
147
194
  } else if (prop.type) {
@@ -164,6 +211,6 @@ module.exports = {
164
211
  findWrappingFunctionTs,
165
212
  findWrappingFunctionJs,
166
213
  extractJsProperties,
167
- extractProperties,
214
+ extractTsProperties,
168
215
  getTypeOfNode,
169
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
  }
@@ -12,6 +12,7 @@ function generateYamlSchema(events, repository, outputPath) {
12
12
  };
13
13
  const options = {
14
14
  noRefs: true,
15
+ lineWidth: -1,
15
16
  };
16
17
  const yamlOutput = yaml.dump(schema, options);
17
18
  const yamlFile = `# yaml-language-server: $schema=${SCHEMA_URL}\n${yamlOutput}`;
@@ -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',