@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.
- package/.github/workflows/pr-check.yml +17 -0
- package/README.md +25 -1
- package/bin/cli.js +7 -2
- package/package.json +1 -1
- package/schema.json +3 -0
- package/src/analyze/analyzeJsFile.js +2 -2
- package/src/analyze/analyzeTsFile.js +4 -4
- package/src/analyze/helpers.js +62 -11
- package/src/analyze/index.js +2 -2
- package/src/index.js +2 -2
- package/tests/extractProperties.test.js +3 -3
|
@@ -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
|
-
-
|
|
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
package/schema.json
CHANGED
|
@@ -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,
|
|
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 =
|
|
33
|
+
const properties = extractTsProperties(checker, propertiesNode);
|
|
34
34
|
events.push({
|
|
35
35
|
eventName,
|
|
36
36
|
source,
|
package/src/analyze/helpers.js
CHANGED
|
@@ -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
|
-
}
|
|
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
|
-
|
|
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
|
-
}
|
|
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
|
-
|
|
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
|
|
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
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
214
|
+
extractTsProperties,
|
|
164
215
|
getTypeOfNode,
|
|
165
216
|
};
|
package/src/analyze/index.js
CHANGED
|
@@ -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
|
-
|
|
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 =
|
|
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 =
|
|
98
|
+
const properties = extractTsProperties(checker, node);
|
|
99
99
|
expect(properties).toEqual({
|
|
100
100
|
address: {
|
|
101
101
|
type: 'object',
|