@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.
@@ -39,10 +39,12 @@ module.exports = __toCommonJS(src_exports);
39
39
  // ../../../../../../../../../../execroot/_main/bazel-out/k8-fastbuild/bin/language/metrics-output-plugin/src/metrics-output.ts
40
40
  var fs = __toESM(require("fs"));
41
41
  var path = __toESM(require("path"));
42
+ var import_ts_deepmerge = require("ts-deepmerge");
42
43
  function normalizePath(filePath) {
43
44
  const normalized = filePath.replace(/\\/g, "/");
44
45
  return normalized.replace(/^file:\/\//, "");
45
46
  }
47
+ var merge = import_ts_deepmerge.merge;
46
48
  var MetricsOutput = class {
47
49
  name = "metrics-output-plugin";
48
50
  outputDir;
@@ -51,7 +53,12 @@ var MetricsOutput = class {
51
53
  stats;
52
54
  features;
53
55
  // In-memory storage of all results
54
- aggregatedResults = {};
56
+ aggregatedResults = {
57
+ content: {}
58
+ };
59
+ get outputFilePath() {
60
+ return path.resolve(this.outputDir, `${this.fileName}.json`);
61
+ }
55
62
  constructor(options = {}) {
56
63
  this.outputDir = options.outputDir || process.cwd();
57
64
  let fileName = options.fileName || "metrics";
@@ -62,36 +69,33 @@ var MetricsOutput = class {
62
69
  this.rootProperties = options.rootProperties || {};
63
70
  this.stats = options.stats || {};
64
71
  this.features = options.features || {};
65
- this.aggregatedResults = {
66
- content: {}
67
- };
68
72
  }
69
73
  apply(service) {
70
74
  service.hooks.onValidateEnd.tap(
71
75
  this.name,
72
76
  (diagnostics, { documentContext }) => {
77
+ if (fs.existsSync(this.outputFilePath)) {
78
+ this.loadExistingMetrics();
79
+ }
73
80
  this.generateFile(diagnostics, documentContext);
74
81
  return diagnostics;
75
82
  }
76
83
  );
77
84
  }
78
- /**
79
- * Evaluates root properties, executing it if it's a function
80
- */
81
- evaluateRootProperties(diagnostics, documentContext) {
82
- if (typeof this.rootProperties === "function") {
83
- try {
84
- const result = this.rootProperties(diagnostics, documentContext);
85
- if (typeof result === "object" && result !== null) {
86
- return result;
87
- }
88
- return { dynamicRootValue: result };
89
- } catch (error) {
90
- documentContext.log.error(`Error evaluating root properties: ${error}`);
91
- return { error: `Root properties evaluation failed: ${error}` };
92
- }
85
+ loadExistingMetrics() {
86
+ try {
87
+ const fileContent = fs.readFileSync(this.outputFilePath, "utf-8");
88
+ const parsed = JSON.parse(fileContent);
89
+ const existingMetrics = parsed && typeof parsed === "object" ? parsed : {};
90
+ this.aggregatedResults = merge(
91
+ existingMetrics,
92
+ this.aggregatedResults
93
+ );
94
+ } catch (error) {
95
+ console.warn(
96
+ `Warning: Could not parse existing metrics file ${this.outputFilePath}. Continuing with current metrics.`
97
+ );
93
98
  }
94
- return this.rootProperties;
95
99
  }
96
100
  /**
97
101
  * Evaluates a value, executing it if it's a function
@@ -108,9 +112,10 @@ var MetricsOutput = class {
108
112
  return value;
109
113
  }
110
114
  generateMetrics(diagnostics, documentContext) {
111
- if (typeof this.stats === "function") {
115
+ const statsSource = this.stats;
116
+ if (typeof statsSource === "function") {
112
117
  try {
113
- const result2 = this.stats(diagnostics, documentContext);
118
+ const result2 = statsSource(diagnostics, documentContext);
114
119
  if (typeof result2 === "object" && result2 !== null) {
115
120
  return result2;
116
121
  }
@@ -121,15 +126,16 @@ var MetricsOutput = class {
121
126
  }
122
127
  }
123
128
  const result = {};
124
- Object.entries(this.stats).forEach(([key, value]) => {
129
+ Object.entries(statsSource).forEach(([key, value]) => {
125
130
  result[key] = this.evaluateValue(value, diagnostics, documentContext);
126
131
  });
127
132
  return result;
128
133
  }
129
134
  generateFeatures(diagnostics, documentContext) {
130
- if (typeof this.features === "function") {
135
+ const featuresSource = this.features;
136
+ if (typeof featuresSource === "function") {
131
137
  try {
132
- const result2 = this.features(diagnostics, documentContext);
138
+ const result2 = featuresSource(diagnostics, documentContext);
133
139
  if (typeof result2 === "object" && result2 !== null) {
134
140
  return result2;
135
141
  }
@@ -142,7 +148,7 @@ var MetricsOutput = class {
142
148
  }
143
149
  }
144
150
  const result = {};
145
- Object.entries(this.features).forEach(([key, value]) => {
151
+ Object.entries(featuresSource).forEach(([key, value]) => {
146
152
  result[key] = this.evaluateValue(value, diagnostics, documentContext);
147
153
  });
148
154
  return result;
@@ -151,18 +157,44 @@ var MetricsOutput = class {
151
157
  const fullOutputDir = path.resolve(process.cwd(), this.outputDir);
152
158
  fs.mkdirSync(fullOutputDir, { recursive: true });
153
159
  const filePath = normalizePath(documentContext.document.uri);
154
- const stats = this.generateMetrics(diagnostics, documentContext);
155
- const features = this.generateFeatures(diagnostics, documentContext);
156
- this.aggregatedResults.content[filePath] = {
160
+ const stats = this.generateMetrics(
161
+ diagnostics,
162
+ documentContext
163
+ );
164
+ const features = this.generateFeatures(
165
+ diagnostics,
166
+ documentContext
167
+ );
168
+ const newEntry = {
157
169
  stats,
158
170
  ...Object.keys(features).length > 0 ? { features } : {}
159
171
  };
160
- const rootProps = this.evaluateRootProperties(diagnostics, documentContext);
161
- Object.assign(this.aggregatedResults, rootProps);
172
+ let rootProps;
173
+ if (typeof this.rootProperties === "function") {
174
+ try {
175
+ const result = this.rootProperties(diagnostics, documentContext);
176
+ if (typeof result === "object" && result !== null) {
177
+ rootProps = result;
178
+ } else {
179
+ rootProps = { dynamicRootValue: result };
180
+ }
181
+ } catch (error) {
182
+ documentContext.log.error(`Error evaluating root properties: ${error}`);
183
+ rootProps = { error: `Root properties evaluation failed: ${error}` };
184
+ }
185
+ } else {
186
+ rootProps = this.rootProperties;
187
+ }
188
+ this.aggregatedResults = merge(
189
+ this.aggregatedResults,
190
+ rootProps,
191
+ { content: { [filePath]: newEntry } }
192
+ );
162
193
  const outputFilePath = path.join(fullOutputDir, `${this.fileName}.json`);
194
+ const { content, ...root } = this.aggregatedResults;
163
195
  fs.writeFileSync(
164
196
  outputFilePath,
165
- JSON.stringify(this.aggregatedResults, null, 2),
197
+ JSON.stringify({ ...root, content }, null, 2),
166
198
  "utf-8"
167
199
  );
168
200
  return outputFilePath;
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../../../../../../execroot/_main/bazel-out/k8-fastbuild/bin/language/metrics-output-plugin/src/index.ts","../../../../../../../../../../../../execroot/_main/bazel-out/k8-fastbuild/bin/language/metrics-output-plugin/src/metrics-output.ts","../../../../../../../../../../../../execroot/_main/bazel-out/k8-fastbuild/bin/language/metrics-output-plugin/src/utils.ts"],"sourcesContent":["export * from \"./metrics-output\";\nexport * from \"./utils\";\n","import * as fs from \"fs\";\nimport * as path from \"path\";\nimport { Diagnostic } from \"vscode-languageserver-types\";\nimport type {\n PlayerLanguageService,\n PlayerLanguageServicePlugin,\n DocumentContext,\n} from \"@player-tools/json-language-service\";\n\n/**\n * Function that will be called with diagnostics and document context\n * @param diagnostics - Array of diagnostics from validation\n * @param documentContext - Context of the current document\n * @returns Any value that will be included in the metrics output\n */\nexport type MetricFunction = (...args: any[]) => any;\n\nexport type MetricValue =\n | Record<string, any>\n | number\n | string\n | boolean\n | MetricFunction;\n\nexport interface MetricsOutputConfig {\n /** Directory where the output file will be written */\n outputDir?: string;\n\n /** Name of the JSON output file */\n fileName?: string;\n\n /**\n * Custom properties to include at the root level of the output\n */\n rootProperties?: Record<string, any> | MetricFunction;\n\n /**\n * Content-specific stats\n */\n stats?: Record<string, MetricValue> | MetricFunction;\n\n /**\n * Content-specific features\n */\n features?: Record<string, MetricValue> | MetricFunction;\n}\n\n/**\n * Normalizes a file path to use consistent separators and format\n */\nfunction normalizePath(filePath: string): string {\n // Convert backslashes to forward slashes for consistency\n const normalized = filePath.replace(/\\\\/g, \"/\");\n\n // Remove file:// protocol if present\n return normalized.replace(/^file:\\/\\//, \"\");\n}\n\n/**\n * A plugin that writes diagnostic results to a JSON file in a specified output directory.\n * NOTE: This plugin is designed for CLI usage only and should not be used in an IDE.\n */\nexport class MetricsOutput implements PlayerLanguageServicePlugin {\n name = \"metrics-output-plugin\";\n\n private outputDir: string;\n private fileName: string;\n private rootProperties: Record<string, any> | MetricFunction;\n private stats: Record<string, MetricValue> | MetricFunction;\n private features: Record<string, MetricValue> | MetricFunction;\n\n // In-memory storage of all results\n private aggregatedResults: Record<string, any> = {};\n\n constructor(options: MetricsOutputConfig = {}) {\n this.outputDir = options.outputDir || process.cwd();\n\n // Handle file name, stripping .json extension if provided\n let fileName = options.fileName || \"metrics\";\n if (fileName.endsWith(\".json\")) {\n fileName = fileName.split(\".\")[0]; // Remove extension\n }\n this.fileName = fileName;\n this.rootProperties = options.rootProperties || {};\n this.stats = options.stats || {};\n this.features = options.features || {};\n\n // Initialize with empty content\n this.aggregatedResults = {\n content: {},\n };\n }\n\n apply(service: PlayerLanguageService): void {\n // Hook into the validation end to capture diagnostics and write output\n service.hooks.onValidateEnd.tap(\n this.name,\n (\n diagnostics: Diagnostic[],\n { documentContext }: { documentContext: DocumentContext },\n ): Diagnostic[] => {\n this.generateFile(diagnostics, documentContext);\n return diagnostics;\n },\n );\n }\n\n /**\n * Evaluates root properties, executing it if it's a function\n */\n private evaluateRootProperties(\n diagnostics: Diagnostic[],\n documentContext: DocumentContext,\n ): Record<string, any> {\n if (typeof this.rootProperties === \"function\") {\n try {\n const result = this.rootProperties(diagnostics, documentContext);\n if (typeof result === \"object\" && result !== null) {\n return result;\n }\n return { dynamicRootValue: result };\n } catch (error) {\n documentContext.log.error(`Error evaluating root properties: ${error}`);\n return { error: `Root properties evaluation failed: ${error}` };\n }\n }\n return this.rootProperties;\n }\n\n /**\n * Evaluates a value, executing it if it's a function\n */\n private evaluateValue(\n value: MetricValue,\n diagnostics: Diagnostic[],\n documentContext: DocumentContext,\n ) {\n if (typeof value === \"function\") {\n try {\n return value(diagnostics, documentContext);\n } catch (error) {\n documentContext.log.error(`Error evaluating value: ${error}`);\n return { error: `Value evaluation failed: ${error}` };\n }\n }\n return value;\n }\n\n private generateMetrics(\n diagnostics: Diagnostic[],\n documentContext: DocumentContext,\n ): Record<string, any> {\n // If stats is a function, evaluate it directly\n if (typeof this.stats === \"function\") {\n try {\n const result = this.stats(diagnostics, documentContext);\n if (typeof result === \"object\" && result !== null) {\n return result;\n }\n return { dynamicStatsValue: result };\n } catch (error) {\n documentContext.log.error(`Error evaluating stats function: ${error}`);\n return { error: `Stats function evaluation failed: ${error}` };\n }\n }\n\n // Otherwise process each metric in the record\n const result: Record<string, any> = {};\n Object.entries(this.stats).forEach(([key, value]) => {\n result[key] = this.evaluateValue(value, diagnostics, documentContext);\n });\n\n return result;\n }\n\n private generateFeatures(\n diagnostics: Diagnostic[],\n documentContext: DocumentContext,\n ): Record<string, any> {\n // If features is a function, evaluate it directly\n if (typeof this.features === \"function\") {\n try {\n const result = this.features(diagnostics, documentContext);\n if (typeof result === \"object\" && result !== null) {\n return result;\n }\n return { dynamicFeaturesValue: result };\n } catch (error) {\n documentContext.log.error(\n `Error evaluating features function: ${error}`,\n );\n return { error: `Features function evaluation failed: ${error}` };\n }\n }\n\n // Otherwise process each feature in the record\n const result: Record<string, any> = {};\n Object.entries(this.features).forEach(([key, value]) => {\n result[key] = this.evaluateValue(value, diagnostics, documentContext);\n });\n\n return result;\n }\n\n private generateFile(\n diagnostics: Diagnostic[],\n documentContext: DocumentContext,\n ): string {\n // Ensure the output directory exists\n const fullOutputDir = path.resolve(process.cwd(), this.outputDir);\n fs.mkdirSync(fullOutputDir, { recursive: true });\n\n // Get the file path from the document URI and normalize it\n const filePath = normalizePath(documentContext.document.uri);\n\n // Generate metrics\n const stats = this.generateMetrics(diagnostics, documentContext);\n const features = this.generateFeatures(diagnostics, documentContext);\n\n // Update content for this file\n this.aggregatedResults.content[filePath] = {\n stats,\n ...(Object.keys(features).length > 0 ? { features } : {}),\n };\n\n // Evaluate root properties with current diagnostics and context\n const rootProps = this.evaluateRootProperties(diagnostics, documentContext);\n\n // Apply root properties to the aggregated results\n Object.assign(this.aggregatedResults, rootProps);\n\n // Write the aggregated results to a file\n const outputFilePath = path.join(fullOutputDir, `${this.fileName}.json`);\n fs.writeFileSync(\n outputFilePath,\n JSON.stringify(this.aggregatedResults, null, 2),\n \"utf-8\",\n );\n\n return outputFilePath;\n }\n}\n","import { Diagnostic } from \"vscode-languageserver-types\";\n\n/**\n * Extracts data from diagnostic messages using a pattern\n */\nexport function extractFromDiagnostics<T>(\n pattern: RegExp,\n parser: (value: string) => T,\n): (diagnostics: Diagnostic[]) => T | undefined {\n return (diagnostics: Diagnostic[]): T | undefined => {\n for (const diagnostic of diagnostics) {\n const match = diagnostic.message.match(pattern);\n if (match && match[1]) {\n try {\n const result = parser(match[1]);\n // Check if result is NaN (only relevant for numeric parsers)\n if (typeof result === \"number\" && isNaN(result)) {\n console.warn(`Failed to parse diagnostic value: ${match[1]}`);\n return undefined;\n }\n return result;\n } catch (e) {\n console.warn(`Failed to parse diagnostic value: ${match[1]}`, e);\n return undefined;\n }\n }\n }\n return undefined;\n };\n}\n\n/**\n * Extracts data from diagnostics\n */\nexport function extractByData(\n data: string | symbol,\n diagnostics: Diagnostic[],\n parser?: (diagnostic: Diagnostic) => any,\n): Record<string, any> {\n const filteredDiagnostics = diagnostics.filter(\n (diagnostic) => diagnostic.data === data,\n );\n if (filteredDiagnostics.length === 0) {\n return {};\n }\n\n // Default parser that attempts to parse the message as JSON or returns the raw message\n const defaultParser = (diagnostic: Diagnostic): any => {\n try {\n if (diagnostic.message) {\n return JSON.parse(diagnostic.message);\n }\n return diagnostic.message || {};\n } catch (e) {\n return diagnostic.message || {};\n }\n };\n\n const actualParser = parser || defaultParser;\n\n // Collect all information from the specified source\n const result: Record<string, any> = {};\n for (const diagnostic of filteredDiagnostics) {\n try {\n const extractedData = actualParser(diagnostic);\n\n if (typeof extractedData === \"object\" && extractedData !== null) {\n // If object, merge with existing data\n Object.assign(result, extractedData);\n } else {\n // Otherwise store as separate entries\n const key = `entry_${Object.keys(result).length}`;\n result[key] = extractedData;\n }\n } catch (e) {\n console.warn(\n `Failed to process diagnostic from data ${String(data)}:`,\n e,\n );\n }\n }\n\n return result; // Always returns an object, even if empty\n}\n"],"mappings":"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","names":["result"]}
1
+ {"version":3,"sources":["../../../../../../../../../../../../execroot/_main/bazel-out/k8-fastbuild/bin/language/metrics-output-plugin/src/index.ts","../../../../../../../../../../../../execroot/_main/bazel-out/k8-fastbuild/bin/language/metrics-output-plugin/src/metrics-output.ts","../../../../../../../../../../../../execroot/_main/bazel-out/k8-fastbuild/bin/language/metrics-output-plugin/src/utils.ts"],"sourcesContent":["export * from \"./metrics-output\";\nexport * from \"./utils\";\n","import * as fs from \"fs\";\nimport * as path from \"path\";\nimport { merge as deepMerge } from \"ts-deepmerge\";\nimport { Diagnostic } from \"vscode-languageserver-types\";\nimport type {\n PlayerLanguageService,\n PlayerLanguageServicePlugin,\n DocumentContext,\n} from \"@player-tools/json-language-service\";\nimport type {\n MetricsRoot,\n MetricsStats,\n MetricsFeatures,\n MetricsContent,\n MetricsReport,\n MetricValue,\n} from \"./types\";\n\nexport interface MetricsOutputConfig {\n /** Directory where the output file will be written */\n outputDir?: string;\n\n /** Name of the JSON output file */\n fileName?: string;\n\n /**\n * Custom properties to include at the root level of the output\n */\n rootProperties?: MetricsRoot;\n\n /**\n * Content-specific stats\n */\n stats?: MetricsStats;\n\n /**\n * Content-specific features\n */\n features?: MetricsFeatures;\n}\n\n/**\n * Normalizes a file path to use consistent separators and format\n */\nfunction normalizePath(filePath: string): string {\n // Convert backslashes to forward slashes for consistency\n const normalized = filePath.replace(/\\\\/g, \"/\");\n\n // Remove file:// protocol if present\n return normalized.replace(/^file:\\/\\//, \"\");\n}\n\n// Narrow ts-deepmerge’s generic return type to what's needed\nconst merge = deepMerge as <T>(...objects: Array<Partial<T>>) => T;\n\n/**\n * A plugin that writes diagnostic results to a JSON file in a specified output directory.\n * NOTE: This plugin is designed for CLI usage only and should not be used in an IDE.\n */\nexport class MetricsOutput implements PlayerLanguageServicePlugin {\n name = \"metrics-output-plugin\";\n\n private outputDir: string;\n private fileName: string;\n private rootProperties: MetricsRoot;\n private stats: MetricsStats;\n private features: MetricsFeatures;\n\n // In-memory storage of all results\n private aggregatedResults: MetricsReport = {\n content: {},\n };\n\n private get outputFilePath(): string {\n return path.resolve(this.outputDir, `${this.fileName}.json`);\n }\n\n constructor(options: MetricsOutputConfig = {}) {\n this.outputDir = options.outputDir || process.cwd();\n\n // Handle file name, stripping .json extension if provided\n let fileName = options.fileName || \"metrics\";\n if (fileName.endsWith(\".json\")) {\n fileName = fileName.split(\".\")[0]; // Remove extension\n }\n this.fileName = fileName;\n this.rootProperties = options.rootProperties || {};\n this.stats = options.stats || {};\n this.features = options.features || {};\n }\n\n apply(service: PlayerLanguageService): void {\n // Hook into the validation end to capture diagnostics and write output\n service.hooks.onValidateEnd.tap(\n this.name,\n (\n diagnostics: Diagnostic[],\n { documentContext }: { documentContext: DocumentContext },\n ): Diagnostic[] => {\n // If metrics file exists, load and append to it\n if (fs.existsSync(this.outputFilePath)) {\n this.loadExistingMetrics();\n }\n\n this.generateFile(diagnostics, documentContext);\n\n return diagnostics;\n },\n );\n }\n\n private loadExistingMetrics(): void {\n try {\n const fileContent = fs.readFileSync(this.outputFilePath, \"utf-8\");\n const parsed: unknown = JSON.parse(fileContent);\n const existingMetrics: Partial<MetricsReport> =\n parsed && typeof parsed === \"object\"\n ? (parsed as Partial<MetricsReport>)\n : {};\n\n // Recursively merge existing metrics with current aggregated results\n this.aggregatedResults = merge<MetricsReport>(\n existingMetrics,\n this.aggregatedResults,\n );\n } catch (error) {\n // If we can't parse existing file, continue with current state\n console.warn(\n `Warning: Could not parse existing metrics file ${this.outputFilePath}. Continuing with current metrics.`,\n );\n }\n }\n\n /**\n * Evaluates a value, executing it if it's a function\n */\n private evaluateValue(\n value: MetricValue,\n diagnostics: Diagnostic[],\n documentContext: DocumentContext,\n ) {\n if (typeof value === \"function\") {\n try {\n return value(diagnostics, documentContext);\n } catch (error) {\n documentContext.log.error(`Error evaluating value: ${error}`);\n return { error: `Value evaluation failed: ${error}` };\n }\n }\n return value;\n }\n\n private generateMetrics(\n diagnostics: Diagnostic[],\n documentContext: DocumentContext,\n ): MetricsStats {\n const statsSource = this.stats;\n // If stats is a function, evaluate it directly\n if (typeof statsSource === \"function\") {\n try {\n const result = statsSource(diagnostics, documentContext);\n if (typeof result === \"object\" && result !== null) {\n return result;\n }\n return { dynamicStatsValue: result };\n } catch (error) {\n documentContext.log.error(`Error evaluating stats function: ${error}`);\n return { error: `Stats function evaluation failed: ${error}` };\n }\n }\n\n // Otherwise process each metric in the record\n const result: MetricsStats = {};\n Object.entries(statsSource).forEach(([key, value]) => {\n result[key] = this.evaluateValue(value, diagnostics, documentContext);\n });\n\n return result;\n }\n\n private generateFeatures(\n diagnostics: Diagnostic[],\n documentContext: DocumentContext,\n ): Record<string, MetricValue> {\n const featuresSource = this.features;\n // If features is a function, evaluate it directly\n if (typeof featuresSource === \"function\") {\n try {\n const result = featuresSource(diagnostics, documentContext);\n if (typeof result === \"object\" && result !== null) {\n return result;\n }\n return { dynamicFeaturesValue: result };\n } catch (error) {\n documentContext.log.error(\n `Error evaluating features function: ${error}`,\n );\n return { error: `Features function evaluation failed: ${error}` };\n }\n }\n\n // Otherwise process each feature in the record\n const result: Record<string, MetricValue> = {};\n Object.entries(featuresSource).forEach(([key, value]) => {\n result[key] = this.evaluateValue(value, diagnostics, documentContext);\n });\n\n return result;\n }\n\n private generateFile(\n diagnostics: Diagnostic[],\n documentContext: DocumentContext,\n ): string {\n // Ensure the output directory exists\n const fullOutputDir = path.resolve(process.cwd(), this.outputDir);\n fs.mkdirSync(fullOutputDir, { recursive: true });\n\n // Get the file path from the document URI and normalize it\n const filePath = normalizePath(documentContext.document.uri);\n\n // Generate metrics\n const stats: MetricsStats = this.generateMetrics(\n diagnostics,\n documentContext,\n );\n const features: MetricsFeatures = this.generateFeatures(\n diagnostics,\n documentContext,\n );\n\n // Build this file's entry\n const newEntry: MetricsContent = {\n stats,\n ...(Object.keys(features).length > 0 ? { features } : {}),\n };\n\n // Evaluate root properties\n let rootProps: MetricsRoot;\n if (typeof this.rootProperties === \"function\") {\n try {\n const result = this.rootProperties(diagnostics, documentContext);\n if (typeof result === \"object\" && result !== null) {\n rootProps = result as Record<string, any>;\n } else {\n rootProps = { dynamicRootValue: result };\n }\n } catch (error) {\n documentContext.log.error(`Error evaluating root properties: ${error}`);\n rootProps = { error: `Root properties evaluation failed: ${error}` };\n }\n } else {\n rootProps = this.rootProperties as Record<string, any>;\n }\n\n // Single deep merge of root properties and content for this file\n this.aggregatedResults = merge<MetricsReport>(\n this.aggregatedResults,\n rootProps,\n { content: { [filePath]: newEntry } },\n );\n\n // Write ordered results: all root properties first, then content last\n const outputFilePath = path.join(fullOutputDir, `${this.fileName}.json`);\n const { content, ...root } = this.aggregatedResults;\n fs.writeFileSync(\n outputFilePath,\n JSON.stringify({ ...root, content }, null, 2),\n \"utf-8\",\n );\n\n return outputFilePath;\n }\n}\n","import { Diagnostic } from \"vscode-languageserver-types\";\n\n/**\n * Extracts data from diagnostic messages using a pattern\n */\nexport function extractFromDiagnostics<T>(\n pattern: RegExp,\n parser: (value: string) => T,\n): (diagnostics: Diagnostic[]) => T | undefined {\n return (diagnostics: Diagnostic[]): T | undefined => {\n for (const diagnostic of diagnostics) {\n const match = diagnostic.message.match(pattern);\n if (match && match[1]) {\n try {\n const result = parser(match[1]);\n // Check if result is NaN (only relevant for numeric parsers)\n if (typeof result === \"number\" && isNaN(result)) {\n console.warn(`Failed to parse diagnostic value: ${match[1]}`);\n return undefined;\n }\n return result;\n } catch (e) {\n console.warn(`Failed to parse diagnostic value: ${match[1]}`, e);\n return undefined;\n }\n }\n }\n return undefined;\n };\n}\n\n/**\n * Extracts data from diagnostics\n */\nexport function extractByData(\n data: string | symbol,\n diagnostics: Diagnostic[],\n parser?: (diagnostic: Diagnostic) => any,\n): Record<string, any> {\n const filteredDiagnostics = diagnostics.filter(\n (diagnostic) => diagnostic.data === data,\n );\n if (filteredDiagnostics.length === 0) {\n return {};\n }\n\n // Default parser that attempts to parse the message as JSON or returns the raw message\n const defaultParser = (diagnostic: Diagnostic): any => {\n try {\n if (diagnostic.message) {\n return JSON.parse(diagnostic.message);\n }\n return diagnostic.message || {};\n } catch (e) {\n return diagnostic.message || {};\n }\n };\n\n const actualParser = parser || defaultParser;\n\n // Collect all information from the specified source\n const result: Record<string, any> = {};\n for (const diagnostic of filteredDiagnostics) {\n try {\n const extractedData = actualParser(diagnostic);\n\n if (typeof extractedData === \"object\" && extractedData !== null) {\n // If object, merge with existing data\n Object.assign(result, extractedData);\n } else {\n // Otherwise store as separate entries\n const key = `entry_${Object.keys(result).length}`;\n result[key] = extractedData;\n }\n } catch (e) {\n console.warn(\n `Failed to process diagnostic from data ${String(data)}:`,\n e,\n );\n }\n }\n\n return result; // Always returns an object, even if empty\n}\n"],"mappings":"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","names":["deepMerge","result"]}
@@ -1,10 +1,12 @@
1
1
  // ../../../../../../../../../../execroot/_main/bazel-out/k8-fastbuild/bin/language/metrics-output-plugin/src/metrics-output.ts
2
2
  import * as fs from "fs";
3
3
  import * as path from "path";
4
+ import { merge as deepMerge } from "ts-deepmerge";
4
5
  function normalizePath(filePath) {
5
6
  const normalized = filePath.replace(/\\/g, "/");
6
7
  return normalized.replace(/^file:\/\//, "");
7
8
  }
9
+ var merge = deepMerge;
8
10
  var MetricsOutput = class {
9
11
  name = "metrics-output-plugin";
10
12
  outputDir;
@@ -13,7 +15,12 @@ var MetricsOutput = class {
13
15
  stats;
14
16
  features;
15
17
  // In-memory storage of all results
16
- aggregatedResults = {};
18
+ aggregatedResults = {
19
+ content: {}
20
+ };
21
+ get outputFilePath() {
22
+ return path.resolve(this.outputDir, `${this.fileName}.json`);
23
+ }
17
24
  constructor(options = {}) {
18
25
  this.outputDir = options.outputDir || process.cwd();
19
26
  let fileName = options.fileName || "metrics";
@@ -24,36 +31,33 @@ var MetricsOutput = class {
24
31
  this.rootProperties = options.rootProperties || {};
25
32
  this.stats = options.stats || {};
26
33
  this.features = options.features || {};
27
- this.aggregatedResults = {
28
- content: {}
29
- };
30
34
  }
31
35
  apply(service) {
32
36
  service.hooks.onValidateEnd.tap(
33
37
  this.name,
34
38
  (diagnostics, { documentContext }) => {
39
+ if (fs.existsSync(this.outputFilePath)) {
40
+ this.loadExistingMetrics();
41
+ }
35
42
  this.generateFile(diagnostics, documentContext);
36
43
  return diagnostics;
37
44
  }
38
45
  );
39
46
  }
40
- /**
41
- * Evaluates root properties, executing it if it's a function
42
- */
43
- evaluateRootProperties(diagnostics, documentContext) {
44
- if (typeof this.rootProperties === "function") {
45
- try {
46
- const result = this.rootProperties(diagnostics, documentContext);
47
- if (typeof result === "object" && result !== null) {
48
- return result;
49
- }
50
- return { dynamicRootValue: result };
51
- } catch (error) {
52
- documentContext.log.error(`Error evaluating root properties: ${error}`);
53
- return { error: `Root properties evaluation failed: ${error}` };
54
- }
47
+ loadExistingMetrics() {
48
+ try {
49
+ const fileContent = fs.readFileSync(this.outputFilePath, "utf-8");
50
+ const parsed = JSON.parse(fileContent);
51
+ const existingMetrics = parsed && typeof parsed === "object" ? parsed : {};
52
+ this.aggregatedResults = merge(
53
+ existingMetrics,
54
+ this.aggregatedResults
55
+ );
56
+ } catch (error) {
57
+ console.warn(
58
+ `Warning: Could not parse existing metrics file ${this.outputFilePath}. Continuing with current metrics.`
59
+ );
55
60
  }
56
- return this.rootProperties;
57
61
  }
58
62
  /**
59
63
  * Evaluates a value, executing it if it's a function
@@ -70,9 +74,10 @@ var MetricsOutput = class {
70
74
  return value;
71
75
  }
72
76
  generateMetrics(diagnostics, documentContext) {
73
- if (typeof this.stats === "function") {
77
+ const statsSource = this.stats;
78
+ if (typeof statsSource === "function") {
74
79
  try {
75
- const result2 = this.stats(diagnostics, documentContext);
80
+ const result2 = statsSource(diagnostics, documentContext);
76
81
  if (typeof result2 === "object" && result2 !== null) {
77
82
  return result2;
78
83
  }
@@ -83,15 +88,16 @@ var MetricsOutput = class {
83
88
  }
84
89
  }
85
90
  const result = {};
86
- Object.entries(this.stats).forEach(([key, value]) => {
91
+ Object.entries(statsSource).forEach(([key, value]) => {
87
92
  result[key] = this.evaluateValue(value, diagnostics, documentContext);
88
93
  });
89
94
  return result;
90
95
  }
91
96
  generateFeatures(diagnostics, documentContext) {
92
- if (typeof this.features === "function") {
97
+ const featuresSource = this.features;
98
+ if (typeof featuresSource === "function") {
93
99
  try {
94
- const result2 = this.features(diagnostics, documentContext);
100
+ const result2 = featuresSource(diagnostics, documentContext);
95
101
  if (typeof result2 === "object" && result2 !== null) {
96
102
  return result2;
97
103
  }
@@ -104,7 +110,7 @@ var MetricsOutput = class {
104
110
  }
105
111
  }
106
112
  const result = {};
107
- Object.entries(this.features).forEach(([key, value]) => {
113
+ Object.entries(featuresSource).forEach(([key, value]) => {
108
114
  result[key] = this.evaluateValue(value, diagnostics, documentContext);
109
115
  });
110
116
  return result;
@@ -113,18 +119,44 @@ var MetricsOutput = class {
113
119
  const fullOutputDir = path.resolve(process.cwd(), this.outputDir);
114
120
  fs.mkdirSync(fullOutputDir, { recursive: true });
115
121
  const filePath = normalizePath(documentContext.document.uri);
116
- const stats = this.generateMetrics(diagnostics, documentContext);
117
- const features = this.generateFeatures(diagnostics, documentContext);
118
- this.aggregatedResults.content[filePath] = {
122
+ const stats = this.generateMetrics(
123
+ diagnostics,
124
+ documentContext
125
+ );
126
+ const features = this.generateFeatures(
127
+ diagnostics,
128
+ documentContext
129
+ );
130
+ const newEntry = {
119
131
  stats,
120
132
  ...Object.keys(features).length > 0 ? { features } : {}
121
133
  };
122
- const rootProps = this.evaluateRootProperties(diagnostics, documentContext);
123
- Object.assign(this.aggregatedResults, rootProps);
134
+ let rootProps;
135
+ if (typeof this.rootProperties === "function") {
136
+ try {
137
+ const result = this.rootProperties(diagnostics, documentContext);
138
+ if (typeof result === "object" && result !== null) {
139
+ rootProps = result;
140
+ } else {
141
+ rootProps = { dynamicRootValue: result };
142
+ }
143
+ } catch (error) {
144
+ documentContext.log.error(`Error evaluating root properties: ${error}`);
145
+ rootProps = { error: `Root properties evaluation failed: ${error}` };
146
+ }
147
+ } else {
148
+ rootProps = this.rootProperties;
149
+ }
150
+ this.aggregatedResults = merge(
151
+ this.aggregatedResults,
152
+ rootProps,
153
+ { content: { [filePath]: newEntry } }
154
+ );
124
155
  const outputFilePath = path.join(fullOutputDir, `${this.fileName}.json`);
156
+ const { content, ...root } = this.aggregatedResults;
125
157
  fs.writeFileSync(
126
158
  outputFilePath,
127
- JSON.stringify(this.aggregatedResults, null, 2),
159
+ JSON.stringify({ ...root, content }, null, 2),
128
160
  "utf-8"
129
161
  );
130
162
  return outputFilePath;
package/dist/index.mjs CHANGED
@@ -1,10 +1,12 @@
1
1
  // ../../../../../../../../../../execroot/_main/bazel-out/k8-fastbuild/bin/language/metrics-output-plugin/src/metrics-output.ts
2
2
  import * as fs from "fs";
3
3
  import * as path from "path";
4
+ import { merge as deepMerge } from "ts-deepmerge";
4
5
  function normalizePath(filePath) {
5
6
  const normalized = filePath.replace(/\\/g, "/");
6
7
  return normalized.replace(/^file:\/\//, "");
7
8
  }
9
+ var merge = deepMerge;
8
10
  var MetricsOutput = class {
9
11
  name = "metrics-output-plugin";
10
12
  outputDir;
@@ -13,7 +15,12 @@ var MetricsOutput = class {
13
15
  stats;
14
16
  features;
15
17
  // In-memory storage of all results
16
- aggregatedResults = {};
18
+ aggregatedResults = {
19
+ content: {}
20
+ };
21
+ get outputFilePath() {
22
+ return path.resolve(this.outputDir, `${this.fileName}.json`);
23
+ }
17
24
  constructor(options = {}) {
18
25
  this.outputDir = options.outputDir || process.cwd();
19
26
  let fileName = options.fileName || "metrics";
@@ -24,36 +31,33 @@ var MetricsOutput = class {
24
31
  this.rootProperties = options.rootProperties || {};
25
32
  this.stats = options.stats || {};
26
33
  this.features = options.features || {};
27
- this.aggregatedResults = {
28
- content: {}
29
- };
30
34
  }
31
35
  apply(service) {
32
36
  service.hooks.onValidateEnd.tap(
33
37
  this.name,
34
38
  (diagnostics, { documentContext }) => {
39
+ if (fs.existsSync(this.outputFilePath)) {
40
+ this.loadExistingMetrics();
41
+ }
35
42
  this.generateFile(diagnostics, documentContext);
36
43
  return diagnostics;
37
44
  }
38
45
  );
39
46
  }
40
- /**
41
- * Evaluates root properties, executing it if it's a function
42
- */
43
- evaluateRootProperties(diagnostics, documentContext) {
44
- if (typeof this.rootProperties === "function") {
45
- try {
46
- const result = this.rootProperties(diagnostics, documentContext);
47
- if (typeof result === "object" && result !== null) {
48
- return result;
49
- }
50
- return { dynamicRootValue: result };
51
- } catch (error) {
52
- documentContext.log.error(`Error evaluating root properties: ${error}`);
53
- return { error: `Root properties evaluation failed: ${error}` };
54
- }
47
+ loadExistingMetrics() {
48
+ try {
49
+ const fileContent = fs.readFileSync(this.outputFilePath, "utf-8");
50
+ const parsed = JSON.parse(fileContent);
51
+ const existingMetrics = parsed && typeof parsed === "object" ? parsed : {};
52
+ this.aggregatedResults = merge(
53
+ existingMetrics,
54
+ this.aggregatedResults
55
+ );
56
+ } catch (error) {
57
+ console.warn(
58
+ `Warning: Could not parse existing metrics file ${this.outputFilePath}. Continuing with current metrics.`
59
+ );
55
60
  }
56
- return this.rootProperties;
57
61
  }
58
62
  /**
59
63
  * Evaluates a value, executing it if it's a function
@@ -70,9 +74,10 @@ var MetricsOutput = class {
70
74
  return value;
71
75
  }
72
76
  generateMetrics(diagnostics, documentContext) {
73
- if (typeof this.stats === "function") {
77
+ const statsSource = this.stats;
78
+ if (typeof statsSource === "function") {
74
79
  try {
75
- const result2 = this.stats(diagnostics, documentContext);
80
+ const result2 = statsSource(diagnostics, documentContext);
76
81
  if (typeof result2 === "object" && result2 !== null) {
77
82
  return result2;
78
83
  }
@@ -83,15 +88,16 @@ var MetricsOutput = class {
83
88
  }
84
89
  }
85
90
  const result = {};
86
- Object.entries(this.stats).forEach(([key, value]) => {
91
+ Object.entries(statsSource).forEach(([key, value]) => {
87
92
  result[key] = this.evaluateValue(value, diagnostics, documentContext);
88
93
  });
89
94
  return result;
90
95
  }
91
96
  generateFeatures(diagnostics, documentContext) {
92
- if (typeof this.features === "function") {
97
+ const featuresSource = this.features;
98
+ if (typeof featuresSource === "function") {
93
99
  try {
94
- const result2 = this.features(diagnostics, documentContext);
100
+ const result2 = featuresSource(diagnostics, documentContext);
95
101
  if (typeof result2 === "object" && result2 !== null) {
96
102
  return result2;
97
103
  }
@@ -104,7 +110,7 @@ var MetricsOutput = class {
104
110
  }
105
111
  }
106
112
  const result = {};
107
- Object.entries(this.features).forEach(([key, value]) => {
113
+ Object.entries(featuresSource).forEach(([key, value]) => {
108
114
  result[key] = this.evaluateValue(value, diagnostics, documentContext);
109
115
  });
110
116
  return result;
@@ -113,18 +119,44 @@ var MetricsOutput = class {
113
119
  const fullOutputDir = path.resolve(process.cwd(), this.outputDir);
114
120
  fs.mkdirSync(fullOutputDir, { recursive: true });
115
121
  const filePath = normalizePath(documentContext.document.uri);
116
- const stats = this.generateMetrics(diagnostics, documentContext);
117
- const features = this.generateFeatures(diagnostics, documentContext);
118
- this.aggregatedResults.content[filePath] = {
122
+ const stats = this.generateMetrics(
123
+ diagnostics,
124
+ documentContext
125
+ );
126
+ const features = this.generateFeatures(
127
+ diagnostics,
128
+ documentContext
129
+ );
130
+ const newEntry = {
119
131
  stats,
120
132
  ...Object.keys(features).length > 0 ? { features } : {}
121
133
  };
122
- const rootProps = this.evaluateRootProperties(diagnostics, documentContext);
123
- Object.assign(this.aggregatedResults, rootProps);
134
+ let rootProps;
135
+ if (typeof this.rootProperties === "function") {
136
+ try {
137
+ const result = this.rootProperties(diagnostics, documentContext);
138
+ if (typeof result === "object" && result !== null) {
139
+ rootProps = result;
140
+ } else {
141
+ rootProps = { dynamicRootValue: result };
142
+ }
143
+ } catch (error) {
144
+ documentContext.log.error(`Error evaluating root properties: ${error}`);
145
+ rootProps = { error: `Root properties evaluation failed: ${error}` };
146
+ }
147
+ } else {
148
+ rootProps = this.rootProperties;
149
+ }
150
+ this.aggregatedResults = merge(
151
+ this.aggregatedResults,
152
+ rootProps,
153
+ { content: { [filePath]: newEntry } }
154
+ );
124
155
  const outputFilePath = path.join(fullOutputDir, `${this.fileName}.json`);
156
+ const { content, ...root } = this.aggregatedResults;
125
157
  fs.writeFileSync(
126
158
  outputFilePath,
127
- JSON.stringify(this.aggregatedResults, null, 2),
159
+ JSON.stringify({ ...root, content }, null, 2),
128
160
  "utf-8"
129
161
  );
130
162
  return outputFilePath;