@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 +9 -1
- package/bin/cli.js +7 -2
- package/package.json +1 -1
- package/schema.json +1 -0
- package/src/analyze/analyzeJsFile.js +2 -2
- package/src/analyze/analyzeTsFile.js +4 -4
- package/src/analyze/helpers.js +58 -11
- package/src/analyze/index.js +2 -2
- package/src/index.js +2 -2
- package/src/yamlGenerator.js +1 -0
- package/tests/extractProperties.test.js +3 -3
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
|
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
|
|
|
@@ -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
|
-
}
|
|
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
|
-
}
|
|
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
|
-
}
|
|
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
|
|
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
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
214
|
+
extractTsProperties,
|
|
168
215
|
getTypeOfNode,
|
|
169
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
|
}
|
package/src/yamlGenerator.js
CHANGED
|
@@ -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
|
-
|
|
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',
|