@player-tools/metrics-output-plugin 0.12.1-next.1 → 0.12.1-next.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/dist/cjs/index.cjs +64 -32
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/index.legacy-esm.js +64 -32
- package/dist/index.mjs +64 -32
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -2
- package/src/__tests__/metrics-output-plugin.test.ts +608 -0
- package/src/metrics-output.ts +98 -66
- package/src/types.ts +21 -0
- package/types/metrics-output.d.ts +6 -15
- package/types/types.d.ts +13 -0
package/src/metrics-output.ts
CHANGED
|
@@ -1,26 +1,20 @@
|
|
|
1
1
|
import * as fs from "fs";
|
|
2
2
|
import * as path from "path";
|
|
3
|
+
import { merge as deepMerge } from "ts-deepmerge";
|
|
3
4
|
import { Diagnostic } from "vscode-languageserver-types";
|
|
4
5
|
import type {
|
|
5
6
|
PlayerLanguageService,
|
|
6
7
|
PlayerLanguageServicePlugin,
|
|
7
8
|
DocumentContext,
|
|
8
9
|
} from "@player-tools/json-language-service";
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
export type MetricValue =
|
|
19
|
-
| Record<string, any>
|
|
20
|
-
| number
|
|
21
|
-
| string
|
|
22
|
-
| boolean
|
|
23
|
-
| MetricFunction;
|
|
10
|
+
import type {
|
|
11
|
+
MetricsRoot,
|
|
12
|
+
MetricsStats,
|
|
13
|
+
MetricsFeatures,
|
|
14
|
+
MetricsContent,
|
|
15
|
+
MetricsReport,
|
|
16
|
+
MetricValue,
|
|
17
|
+
} from "./types";
|
|
24
18
|
|
|
25
19
|
export interface MetricsOutputConfig {
|
|
26
20
|
/** Directory where the output file will be written */
|
|
@@ -32,17 +26,17 @@ export interface MetricsOutputConfig {
|
|
|
32
26
|
/**
|
|
33
27
|
* Custom properties to include at the root level of the output
|
|
34
28
|
*/
|
|
35
|
-
rootProperties?:
|
|
29
|
+
rootProperties?: MetricsRoot;
|
|
36
30
|
|
|
37
31
|
/**
|
|
38
32
|
* Content-specific stats
|
|
39
33
|
*/
|
|
40
|
-
stats?:
|
|
34
|
+
stats?: MetricsStats;
|
|
41
35
|
|
|
42
36
|
/**
|
|
43
37
|
* Content-specific features
|
|
44
38
|
*/
|
|
45
|
-
features?:
|
|
39
|
+
features?: MetricsFeatures;
|
|
46
40
|
}
|
|
47
41
|
|
|
48
42
|
/**
|
|
@@ -56,6 +50,9 @@ function normalizePath(filePath: string): string {
|
|
|
56
50
|
return normalized.replace(/^file:\/\//, "");
|
|
57
51
|
}
|
|
58
52
|
|
|
53
|
+
// Narrow ts-deepmerge’s generic return type to what's needed
|
|
54
|
+
const merge = deepMerge as <T>(...objects: Array<Partial<T>>) => T;
|
|
55
|
+
|
|
59
56
|
/**
|
|
60
57
|
* A plugin that writes diagnostic results to a JSON file in a specified output directory.
|
|
61
58
|
* NOTE: This plugin is designed for CLI usage only and should not be used in an IDE.
|
|
@@ -65,12 +62,18 @@ export class MetricsOutput implements PlayerLanguageServicePlugin {
|
|
|
65
62
|
|
|
66
63
|
private outputDir: string;
|
|
67
64
|
private fileName: string;
|
|
68
|
-
private rootProperties:
|
|
69
|
-
private stats:
|
|
70
|
-
private features:
|
|
65
|
+
private rootProperties: MetricsRoot;
|
|
66
|
+
private stats: MetricsStats;
|
|
67
|
+
private features: MetricsFeatures;
|
|
71
68
|
|
|
72
69
|
// In-memory storage of all results
|
|
73
|
-
private aggregatedResults:
|
|
70
|
+
private aggregatedResults: MetricsReport = {
|
|
71
|
+
content: {},
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
private get outputFilePath(): string {
|
|
75
|
+
return path.resolve(this.outputDir, `${this.fileName}.json`);
|
|
76
|
+
}
|
|
74
77
|
|
|
75
78
|
constructor(options: MetricsOutputConfig = {}) {
|
|
76
79
|
this.outputDir = options.outputDir || process.cwd();
|
|
@@ -84,11 +87,6 @@ export class MetricsOutput implements PlayerLanguageServicePlugin {
|
|
|
84
87
|
this.rootProperties = options.rootProperties || {};
|
|
85
88
|
this.stats = options.stats || {};
|
|
86
89
|
this.features = options.features || {};
|
|
87
|
-
|
|
88
|
-
// Initialize with empty content
|
|
89
|
-
this.aggregatedResults = {
|
|
90
|
-
content: {},
|
|
91
|
-
};
|
|
92
90
|
}
|
|
93
91
|
|
|
94
92
|
apply(service: PlayerLanguageService): void {
|
|
@@ -99,32 +97,38 @@ export class MetricsOutput implements PlayerLanguageServicePlugin {
|
|
|
99
97
|
diagnostics: Diagnostic[],
|
|
100
98
|
{ documentContext }: { documentContext: DocumentContext },
|
|
101
99
|
): Diagnostic[] => {
|
|
100
|
+
// If metrics file exists, load and append to it
|
|
101
|
+
if (fs.existsSync(this.outputFilePath)) {
|
|
102
|
+
this.loadExistingMetrics();
|
|
103
|
+
}
|
|
104
|
+
|
|
102
105
|
this.generateFile(diagnostics, documentContext);
|
|
106
|
+
|
|
103
107
|
return diagnostics;
|
|
104
108
|
},
|
|
105
109
|
);
|
|
106
110
|
}
|
|
107
111
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
112
|
+
private loadExistingMetrics(): void {
|
|
113
|
+
try {
|
|
114
|
+
const fileContent = fs.readFileSync(this.outputFilePath, "utf-8");
|
|
115
|
+
const parsed: unknown = JSON.parse(fileContent);
|
|
116
|
+
const existingMetrics: Partial<MetricsReport> =
|
|
117
|
+
parsed && typeof parsed === "object"
|
|
118
|
+
? (parsed as Partial<MetricsReport>)
|
|
119
|
+
: {};
|
|
120
|
+
|
|
121
|
+
// Recursively merge existing metrics with current aggregated results
|
|
122
|
+
this.aggregatedResults = merge<MetricsReport>(
|
|
123
|
+
existingMetrics,
|
|
124
|
+
this.aggregatedResults,
|
|
125
|
+
);
|
|
126
|
+
} catch (error) {
|
|
127
|
+
// If we can't parse existing file, continue with current state
|
|
128
|
+
console.warn(
|
|
129
|
+
`Warning: Could not parse existing metrics file ${this.outputFilePath}. Continuing with current metrics.`,
|
|
130
|
+
);
|
|
126
131
|
}
|
|
127
|
-
return this.rootProperties;
|
|
128
132
|
}
|
|
129
133
|
|
|
130
134
|
/**
|
|
@@ -149,11 +153,12 @@ export class MetricsOutput implements PlayerLanguageServicePlugin {
|
|
|
149
153
|
private generateMetrics(
|
|
150
154
|
diagnostics: Diagnostic[],
|
|
151
155
|
documentContext: DocumentContext,
|
|
152
|
-
):
|
|
156
|
+
): MetricsStats {
|
|
157
|
+
const statsSource = this.stats;
|
|
153
158
|
// If stats is a function, evaluate it directly
|
|
154
|
-
if (typeof
|
|
159
|
+
if (typeof statsSource === "function") {
|
|
155
160
|
try {
|
|
156
|
-
const result =
|
|
161
|
+
const result = statsSource(diagnostics, documentContext);
|
|
157
162
|
if (typeof result === "object" && result !== null) {
|
|
158
163
|
return result;
|
|
159
164
|
}
|
|
@@ -165,8 +170,8 @@ export class MetricsOutput implements PlayerLanguageServicePlugin {
|
|
|
165
170
|
}
|
|
166
171
|
|
|
167
172
|
// Otherwise process each metric in the record
|
|
168
|
-
const result:
|
|
169
|
-
Object.entries(
|
|
173
|
+
const result: MetricsStats = {};
|
|
174
|
+
Object.entries(statsSource).forEach(([key, value]) => {
|
|
170
175
|
result[key] = this.evaluateValue(value, diagnostics, documentContext);
|
|
171
176
|
});
|
|
172
177
|
|
|
@@ -176,11 +181,12 @@ export class MetricsOutput implements PlayerLanguageServicePlugin {
|
|
|
176
181
|
private generateFeatures(
|
|
177
182
|
diagnostics: Diagnostic[],
|
|
178
183
|
documentContext: DocumentContext,
|
|
179
|
-
): Record<string,
|
|
184
|
+
): Record<string, MetricValue> {
|
|
185
|
+
const featuresSource = this.features;
|
|
180
186
|
// If features is a function, evaluate it directly
|
|
181
|
-
if (typeof
|
|
187
|
+
if (typeof featuresSource === "function") {
|
|
182
188
|
try {
|
|
183
|
-
const result =
|
|
189
|
+
const result = featuresSource(diagnostics, documentContext);
|
|
184
190
|
if (typeof result === "object" && result !== null) {
|
|
185
191
|
return result;
|
|
186
192
|
}
|
|
@@ -194,8 +200,8 @@ export class MetricsOutput implements PlayerLanguageServicePlugin {
|
|
|
194
200
|
}
|
|
195
201
|
|
|
196
202
|
// Otherwise process each feature in the record
|
|
197
|
-
const result: Record<string,
|
|
198
|
-
Object.entries(
|
|
203
|
+
const result: Record<string, MetricValue> = {};
|
|
204
|
+
Object.entries(featuresSource).forEach(([key, value]) => {
|
|
199
205
|
result[key] = this.evaluateValue(value, diagnostics, documentContext);
|
|
200
206
|
});
|
|
201
207
|
|
|
@@ -214,26 +220,52 @@ export class MetricsOutput implements PlayerLanguageServicePlugin {
|
|
|
214
220
|
const filePath = normalizePath(documentContext.document.uri);
|
|
215
221
|
|
|
216
222
|
// Generate metrics
|
|
217
|
-
const stats = this.generateMetrics(
|
|
218
|
-
|
|
223
|
+
const stats: MetricsStats = this.generateMetrics(
|
|
224
|
+
diagnostics,
|
|
225
|
+
documentContext,
|
|
226
|
+
);
|
|
227
|
+
const features: MetricsFeatures = this.generateFeatures(
|
|
228
|
+
diagnostics,
|
|
229
|
+
documentContext,
|
|
230
|
+
);
|
|
219
231
|
|
|
220
|
-
//
|
|
221
|
-
|
|
232
|
+
// Build this file's entry
|
|
233
|
+
const newEntry: MetricsContent = {
|
|
222
234
|
stats,
|
|
223
235
|
...(Object.keys(features).length > 0 ? { features } : {}),
|
|
224
236
|
};
|
|
225
237
|
|
|
226
|
-
// Evaluate root properties
|
|
227
|
-
|
|
238
|
+
// Evaluate root properties
|
|
239
|
+
let rootProps: MetricsRoot;
|
|
240
|
+
if (typeof this.rootProperties === "function") {
|
|
241
|
+
try {
|
|
242
|
+
const result = this.rootProperties(diagnostics, documentContext);
|
|
243
|
+
if (typeof result === "object" && result !== null) {
|
|
244
|
+
rootProps = result as Record<string, any>;
|
|
245
|
+
} else {
|
|
246
|
+
rootProps = { dynamicRootValue: result };
|
|
247
|
+
}
|
|
248
|
+
} catch (error) {
|
|
249
|
+
documentContext.log.error(`Error evaluating root properties: ${error}`);
|
|
250
|
+
rootProps = { error: `Root properties evaluation failed: ${error}` };
|
|
251
|
+
}
|
|
252
|
+
} else {
|
|
253
|
+
rootProps = this.rootProperties as Record<string, any>;
|
|
254
|
+
}
|
|
228
255
|
|
|
229
|
-
//
|
|
230
|
-
|
|
256
|
+
// Single deep merge of root properties and content for this file
|
|
257
|
+
this.aggregatedResults = merge<MetricsReport>(
|
|
258
|
+
this.aggregatedResults,
|
|
259
|
+
rootProps,
|
|
260
|
+
{ content: { [filePath]: newEntry } },
|
|
261
|
+
);
|
|
231
262
|
|
|
232
|
-
// Write
|
|
263
|
+
// Write ordered results: all root properties first, then content last
|
|
233
264
|
const outputFilePath = path.join(fullOutputDir, `${this.fileName}.json`);
|
|
265
|
+
const { content, ...root } = this.aggregatedResults;
|
|
234
266
|
fs.writeFileSync(
|
|
235
267
|
outputFilePath,
|
|
236
|
-
JSON.stringify(
|
|
268
|
+
JSON.stringify({ ...root, content }, null, 2),
|
|
237
269
|
"utf-8",
|
|
238
270
|
);
|
|
239
271
|
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export type MetricFunction = (...args: any[]) => any;
|
|
2
|
+
|
|
3
|
+
export type MetricValue =
|
|
4
|
+
| Record<string, unknown>
|
|
5
|
+
| number
|
|
6
|
+
| string
|
|
7
|
+
| boolean
|
|
8
|
+
| MetricFunction;
|
|
9
|
+
|
|
10
|
+
export type MetricsRoot = Record<string, MetricValue> | MetricFunction;
|
|
11
|
+
export type MetricsStats = Record<string, MetricValue> | MetricFunction;
|
|
12
|
+
export type MetricsFeatures = Record<string, MetricValue> | MetricFunction;
|
|
13
|
+
|
|
14
|
+
export type MetricsContent = {
|
|
15
|
+
stats: MetricsStats;
|
|
16
|
+
features?: MetricsFeatures;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export type MetricsReport = MetricsRoot & {
|
|
20
|
+
content: Record<string, MetricsContent>;
|
|
21
|
+
};
|
|
@@ -1,12 +1,5 @@
|
|
|
1
1
|
import type { PlayerLanguageService, PlayerLanguageServicePlugin } from "@player-tools/json-language-service";
|
|
2
|
-
|
|
3
|
-
* Function that will be called with diagnostics and document context
|
|
4
|
-
* @param diagnostics - Array of diagnostics from validation
|
|
5
|
-
* @param documentContext - Context of the current document
|
|
6
|
-
* @returns Any value that will be included in the metrics output
|
|
7
|
-
*/
|
|
8
|
-
export type MetricFunction = (...args: any[]) => any;
|
|
9
|
-
export type MetricValue = Record<string, any> | number | string | boolean | MetricFunction;
|
|
2
|
+
import type { MetricsRoot, MetricsStats, MetricsFeatures } from "./types";
|
|
10
3
|
export interface MetricsOutputConfig {
|
|
11
4
|
/** Directory where the output file will be written */
|
|
12
5
|
outputDir?: string;
|
|
@@ -15,15 +8,15 @@ export interface MetricsOutputConfig {
|
|
|
15
8
|
/**
|
|
16
9
|
* Custom properties to include at the root level of the output
|
|
17
10
|
*/
|
|
18
|
-
rootProperties?:
|
|
11
|
+
rootProperties?: MetricsRoot;
|
|
19
12
|
/**
|
|
20
13
|
* Content-specific stats
|
|
21
14
|
*/
|
|
22
|
-
stats?:
|
|
15
|
+
stats?: MetricsStats;
|
|
23
16
|
/**
|
|
24
17
|
* Content-specific features
|
|
25
18
|
*/
|
|
26
|
-
features?:
|
|
19
|
+
features?: MetricsFeatures;
|
|
27
20
|
}
|
|
28
21
|
/**
|
|
29
22
|
* A plugin that writes diagnostic results to a JSON file in a specified output directory.
|
|
@@ -37,12 +30,10 @@ export declare class MetricsOutput implements PlayerLanguageServicePlugin {
|
|
|
37
30
|
private stats;
|
|
38
31
|
private features;
|
|
39
32
|
private aggregatedResults;
|
|
33
|
+
private get outputFilePath();
|
|
40
34
|
constructor(options?: MetricsOutputConfig);
|
|
41
35
|
apply(service: PlayerLanguageService): void;
|
|
42
|
-
|
|
43
|
-
* Evaluates root properties, executing it if it's a function
|
|
44
|
-
*/
|
|
45
|
-
private evaluateRootProperties;
|
|
36
|
+
private loadExistingMetrics;
|
|
46
37
|
/**
|
|
47
38
|
* Evaluates a value, executing it if it's a function
|
|
48
39
|
*/
|
package/types/types.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export type MetricFunction = (...args: any[]) => any;
|
|
2
|
+
export type MetricValue = Record<string, unknown> | number | string | boolean | MetricFunction;
|
|
3
|
+
export type MetricsRoot = Record<string, MetricValue> | MetricFunction;
|
|
4
|
+
export type MetricsStats = Record<string, MetricValue> | MetricFunction;
|
|
5
|
+
export type MetricsFeatures = Record<string, MetricValue> | MetricFunction;
|
|
6
|
+
export type MetricsContent = {
|
|
7
|
+
stats: MetricsStats;
|
|
8
|
+
features?: MetricsFeatures;
|
|
9
|
+
};
|
|
10
|
+
export type MetricsReport = MetricsRoot & {
|
|
11
|
+
content: Record<string, MetricsContent>;
|
|
12
|
+
};
|
|
13
|
+
//# sourceMappingURL=types.d.ts.map
|