@flisk/analyze-tracking 0.1.0 → 0.1.1
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/npm-publish.yml +33 -0
- package/README.md +4 -0
- package/jest.config.js +7 -0
- package/package.json +5 -2
- package/src/analyze/analyzeJsFile.js +1 -1
- package/src/analyze/helpers.js +29 -5
- package/tests/detectSource.test.js +20 -0
- package/tests/extractProperties.test.js +109 -0
- package/tests/findWrappingFunction.test.js +30 -0
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
name: Publish Package to NPM
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types: [published]
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
test:
|
|
9
|
+
runs-on: ubuntu-latest
|
|
10
|
+
steps:
|
|
11
|
+
- uses: actions/checkout@v4
|
|
12
|
+
- uses: actions/setup-node@v4
|
|
13
|
+
with:
|
|
14
|
+
node-version: 20
|
|
15
|
+
- run: npm ci
|
|
16
|
+
- run: npm test
|
|
17
|
+
|
|
18
|
+
publish:
|
|
19
|
+
needs: test
|
|
20
|
+
runs-on: ubuntu-latest
|
|
21
|
+
permissions:
|
|
22
|
+
contents: read
|
|
23
|
+
id-token: write
|
|
24
|
+
steps:
|
|
25
|
+
- uses: actions/checkout@v4
|
|
26
|
+
- uses: actions/setup-node@v4
|
|
27
|
+
with:
|
|
28
|
+
node-version: 20
|
|
29
|
+
registry-url: https://registry.npmjs.org/
|
|
30
|
+
- run: npm ci
|
|
31
|
+
- run: npm publish --provenance --access public
|
|
32
|
+
env:
|
|
33
|
+
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
package/README.md
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
# @flisk/analyze-tracking
|
|
2
|
+
|
|
2
3
|
Analyzes tracking code in a project and generates data schemas
|
|
3
4
|
|
|
5
|
+
[](https://www.npmjs.com/package/@flisk/analyze-tracking)
|
|
6
|
+
|
|
7
|
+
|
|
4
8
|
## Usage
|
|
5
9
|
```sh
|
|
6
10
|
npx @flisk/analyze-tracking /path/to/project
|
package/jest.config.js
ADDED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@flisk/analyze-tracking",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "Analyzes tracking code in a project and generates data schemas",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
},
|
|
9
9
|
"scripts": {
|
|
10
10
|
"start": "node bin/analyze-tracking.js",
|
|
11
|
-
"test": "
|
|
11
|
+
"test": "jest"
|
|
12
12
|
},
|
|
13
13
|
"repository": {
|
|
14
14
|
"type": "git",
|
|
@@ -26,5 +26,8 @@
|
|
|
26
26
|
"acorn-walk": "^8.3.3",
|
|
27
27
|
"js-yaml": "^4.1.0",
|
|
28
28
|
"typescript": "^5.5.4"
|
|
29
|
+
},
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"jest": "^29.7.0"
|
|
29
32
|
}
|
|
30
33
|
}
|
|
@@ -29,7 +29,7 @@ function analyzeJsFile(filePath) {
|
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
const line = node.loc.start.line;
|
|
32
|
-
const functionName = findWrappingFunctionJs(ancestors
|
|
32
|
+
const functionName = findWrappingFunctionJs(node, ancestors);
|
|
33
33
|
|
|
34
34
|
if (eventName && propertiesNode && propertiesNode.type === 'ObjectExpression') {
|
|
35
35
|
const properties = extractJsProperties(propertiesNode);
|
package/src/analyze/helpers.js
CHANGED
|
@@ -53,13 +53,37 @@ function findWrappingFunctionTs(node) {
|
|
|
53
53
|
return 'global';
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
-
function findWrappingFunctionJs(node) {
|
|
57
|
-
let
|
|
58
|
-
|
|
59
|
-
|
|
56
|
+
function findWrappingFunctionJs(node, ancestors) {
|
|
57
|
+
for (let i = ancestors.length - 1; i >= 0; i--) {
|
|
58
|
+
const current = ancestors[i];
|
|
59
|
+
|
|
60
|
+
// Handle direct variable assignments (e.g., const myFunc = () => {})
|
|
61
|
+
if (current.type === 'VariableDeclarator' && current.init === node) {
|
|
62
|
+
return current.id.name;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Handle arrow functions or function expressions assigned to variables
|
|
66
|
+
if (current.type === 'VariableDeclarator' && (current.init.type === 'ArrowFunctionExpression' || current.init.type === 'FunctionExpression')) {
|
|
67
|
+
return current.id.name;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Handle named function declarations
|
|
71
|
+
if (current.type === 'FunctionDeclaration') {
|
|
60
72
|
return current.id ? current.id.name : 'anonymous';
|
|
61
73
|
}
|
|
62
|
-
|
|
74
|
+
|
|
75
|
+
// Handle exported variable/function (e.g., export const myFunc = () => {})
|
|
76
|
+
if (current.type === 'ExportNamedDeclaration' && current.declaration) {
|
|
77
|
+
const declaration = current.declaration.declarations ? current.declaration.declarations[0] : null;
|
|
78
|
+
if (declaration && (declaration.init.type === 'ArrowFunctionExpression' || declaration.init.type === 'FunctionExpression')) {
|
|
79
|
+
return declaration.id.name;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Handle methods within object literals
|
|
84
|
+
if (current.type === 'Property' && current.value === node) {
|
|
85
|
+
return current.key.name || current.key.value;
|
|
86
|
+
}
|
|
63
87
|
}
|
|
64
88
|
return 'global';
|
|
65
89
|
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
const {
|
|
2
|
+
detectSourceJs,
|
|
3
|
+
} = require('../src/analyze/helpers');
|
|
4
|
+
|
|
5
|
+
describe('detectSourceJs', () => {
|
|
6
|
+
it('should detect Google Analytics', () => {
|
|
7
|
+
const node = { callee: { type: 'Identifier', name: 'gtag' } };
|
|
8
|
+
expect(detectSourceJs(node)).toBe('googleanalytics');
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it('should detect Segment', () => {
|
|
12
|
+
const node = { callee: { type: 'MemberExpression', object: { name: 'analytics' }, property: { name: 'track' } } };
|
|
13
|
+
expect(detectSourceJs(node)).toBe('segment');
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it('should return unknown for unrecognized source', () => {
|
|
17
|
+
const node = { callee: { type: 'Identifier', name: 'unknownLib' } };
|
|
18
|
+
expect(detectSourceJs(node)).toBe('unknown');
|
|
19
|
+
});
|
|
20
|
+
});
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
const ts = require('typescript');
|
|
2
|
+
const {
|
|
3
|
+
extractJsProperties,
|
|
4
|
+
extractProperties,
|
|
5
|
+
} = require('../src/analyze/helpers');
|
|
6
|
+
|
|
7
|
+
describe('extractJsProperties', () => {
|
|
8
|
+
it('should extract simple properties', () => {
|
|
9
|
+
const node = {
|
|
10
|
+
properties: [
|
|
11
|
+
{ key: { name: 'userId' }, value: { value: '12345', type: 'Literal' } },
|
|
12
|
+
{ key: { name: 'plan' }, value: { value: 'Free', type: 'Literal' } },
|
|
13
|
+
],
|
|
14
|
+
};
|
|
15
|
+
const properties = extractJsProperties(node);
|
|
16
|
+
expect(properties).toEqual({
|
|
17
|
+
userId: { type: 'string' },
|
|
18
|
+
plan: { type: 'string' },
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('should handle nested object properties', () => {
|
|
23
|
+
const node = {
|
|
24
|
+
properties: [
|
|
25
|
+
{
|
|
26
|
+
key: { name: 'address' },
|
|
27
|
+
value: {
|
|
28
|
+
type: 'ObjectExpression',
|
|
29
|
+
properties: [
|
|
30
|
+
{ key: { name: 'city' }, value: { value: 'San Francisco', type: 'Literal' } },
|
|
31
|
+
{ key: { name: 'state' }, value: { value: 'CA', type: 'Literal' } },
|
|
32
|
+
],
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
],
|
|
36
|
+
};
|
|
37
|
+
const properties = extractJsProperties(node);
|
|
38
|
+
expect(properties).toEqual({
|
|
39
|
+
address: {
|
|
40
|
+
type: 'object',
|
|
41
|
+
properties: {
|
|
42
|
+
city: { type: 'string' },
|
|
43
|
+
state: { type: 'string' },
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('should handle properties with undefined type', () => {
|
|
50
|
+
const node = {
|
|
51
|
+
properties: [{ key: { name: 'undefinedProp' }, value: { value: undefined, type: 'Literal' } }],
|
|
52
|
+
};
|
|
53
|
+
const properties = extractJsProperties(node);
|
|
54
|
+
expect(properties).toEqual({
|
|
55
|
+
undefinedProp: { type: 'any' },
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
describe('extractTsProperties', () => {
|
|
61
|
+
it('should extract properties from TypeScript object', () => {
|
|
62
|
+
const node = {
|
|
63
|
+
properties: [
|
|
64
|
+
{ name: { text: 'userId' }, initializer: { text: '12345', type: 'Literal' } },
|
|
65
|
+
{ name: { text: 'plan' }, initializer: { text: 'Free', type: 'Literal' } },
|
|
66
|
+
],
|
|
67
|
+
};
|
|
68
|
+
const checker = {
|
|
69
|
+
getTypeAtLocation: jest.fn().mockReturnValue({}),
|
|
70
|
+
typeToString: jest.fn().mockReturnValue('string'),
|
|
71
|
+
};
|
|
72
|
+
const properties = extractProperties(checker, node);
|
|
73
|
+
expect(properties).toEqual({
|
|
74
|
+
userId: { type: 'string' },
|
|
75
|
+
plan: { type: 'string' },
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('should handle nested object properties in TypeScript', () => {
|
|
80
|
+
const node = {
|
|
81
|
+
properties: [
|
|
82
|
+
{
|
|
83
|
+
name: { text: 'address' },
|
|
84
|
+
initializer: {
|
|
85
|
+
kind: ts.SyntaxKind.ObjectLiteralExpression,
|
|
86
|
+
properties: [
|
|
87
|
+
{ name: { text: 'city' }, initializer: { text: 'San Francisco', type: 'Literal' } },
|
|
88
|
+
{ name: { text: 'state' }, initializer: { text: 'CA', type: 'Literal' } },
|
|
89
|
+
],
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
],
|
|
93
|
+
};
|
|
94
|
+
const checker = {
|
|
95
|
+
getTypeAtLocation: jest.fn().mockReturnValue({}),
|
|
96
|
+
typeToString: jest.fn().mockReturnValue('string'),
|
|
97
|
+
};
|
|
98
|
+
const properties = extractProperties(checker, node);
|
|
99
|
+
expect(properties).toEqual({
|
|
100
|
+
address: {
|
|
101
|
+
type: 'object',
|
|
102
|
+
properties: {
|
|
103
|
+
city: { type: 'string' },
|
|
104
|
+
state: { type: 'string' },
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
});
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
const ts = require('typescript');
|
|
2
|
+
const {
|
|
3
|
+
findWrappingFunctionJs,
|
|
4
|
+
} = require('../src/analyze/helpers');
|
|
5
|
+
|
|
6
|
+
describe('findWrappingFunctionJs', () => {
|
|
7
|
+
it('should return function name for arrow function assigned to variable', () => {
|
|
8
|
+
const node = { type: 'ArrowFunctionExpression' };
|
|
9
|
+
const ancestors = [
|
|
10
|
+
{ type: 'Program' },
|
|
11
|
+
{ type: 'VariableDeclarator', init: node, id: { name: 'checkout' } },
|
|
12
|
+
];
|
|
13
|
+
expect(findWrappingFunctionJs(node, ancestors)).toBe('checkout');
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it('should return function name for function expression assigned to variable', () => {
|
|
17
|
+
const node = { type: 'FunctionExpression' };
|
|
18
|
+
const ancestors = [
|
|
19
|
+
{ type: 'Program' },
|
|
20
|
+
{ type: 'VariableDeclarator', init: node, id: { name: 'myFunc' } },
|
|
21
|
+
];
|
|
22
|
+
expect(findWrappingFunctionJs(node, ancestors)).toBe('myFunc');
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('should return "global" if no wrapping function is found', () => {
|
|
26
|
+
const node = {};
|
|
27
|
+
const ancestors = [{ type: 'Program' }];
|
|
28
|
+
expect(findWrappingFunctionJs(node, ancestors)).toBe('global');
|
|
29
|
+
});
|
|
30
|
+
});
|