@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.
@@ -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
- * Function that will be called with diagnostics and document context
12
- * @param diagnostics - Array of diagnostics from validation
13
- * @param documentContext - Context of the current document
14
- * @returns Any value that will be included in the metrics output
15
- */
16
- export type MetricFunction = (...args: any[]) => any;
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?: Record<string, any> | MetricFunction;
29
+ rootProperties?: MetricsRoot;
36
30
 
37
31
  /**
38
32
  * Content-specific stats
39
33
  */
40
- stats?: Record<string, MetricValue> | MetricFunction;
34
+ stats?: MetricsStats;
41
35
 
42
36
  /**
43
37
  * Content-specific features
44
38
  */
45
- features?: Record<string, MetricValue> | MetricFunction;
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: Record<string, any> | MetricFunction;
69
- private stats: Record<string, MetricValue> | MetricFunction;
70
- private features: Record<string, MetricValue> | MetricFunction;
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: Record<string, any> = {};
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
- * Evaluates root properties, executing it if it's a function
110
- */
111
- private evaluateRootProperties(
112
- diagnostics: Diagnostic[],
113
- documentContext: DocumentContext,
114
- ): Record<string, any> {
115
- if (typeof this.rootProperties === "function") {
116
- try {
117
- const result = this.rootProperties(diagnostics, documentContext);
118
- if (typeof result === "object" && result !== null) {
119
- return result;
120
- }
121
- return { dynamicRootValue: result };
122
- } catch (error) {
123
- documentContext.log.error(`Error evaluating root properties: ${error}`);
124
- return { error: `Root properties evaluation failed: ${error}` };
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
- ): Record<string, any> {
156
+ ): MetricsStats {
157
+ const statsSource = this.stats;
153
158
  // If stats is a function, evaluate it directly
154
- if (typeof this.stats === "function") {
159
+ if (typeof statsSource === "function") {
155
160
  try {
156
- const result = this.stats(diagnostics, documentContext);
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: Record<string, any> = {};
169
- Object.entries(this.stats).forEach(([key, value]) => {
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, any> {
184
+ ): Record<string, MetricValue> {
185
+ const featuresSource = this.features;
180
186
  // If features is a function, evaluate it directly
181
- if (typeof this.features === "function") {
187
+ if (typeof featuresSource === "function") {
182
188
  try {
183
- const result = this.features(diagnostics, documentContext);
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, any> = {};
198
- Object.entries(this.features).forEach(([key, value]) => {
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(diagnostics, documentContext);
218
- const features = this.generateFeatures(diagnostics, documentContext);
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
- // Update content for this file
221
- this.aggregatedResults.content[filePath] = {
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 with current diagnostics and context
227
- const rootProps = this.evaluateRootProperties(diagnostics, documentContext);
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
- // Apply root properties to the aggregated results
230
- Object.assign(this.aggregatedResults, rootProps);
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 the aggregated results to a file
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(this.aggregatedResults, null, 2),
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?: Record<string, any> | MetricFunction;
11
+ rootProperties?: MetricsRoot;
19
12
  /**
20
13
  * Content-specific stats
21
14
  */
22
- stats?: Record<string, MetricValue> | MetricFunction;
15
+ stats?: MetricsStats;
23
16
  /**
24
17
  * Content-specific features
25
18
  */
26
- features?: Record<string, MetricValue> | MetricFunction;
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
  */
@@ -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