@herb-tools/config 0.8.9 → 0.9.0
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 +1 -1
- package/dist/{src/config-schema.js → config-schema.js} +9 -0
- package/dist/config-schema.js.map +1 -0
- package/dist/{src/config.js → config.js} +73 -83
- package/dist/config.js.map +1 -0
- package/dist/herb-config.cjs +92 -94
- package/dist/herb-config.cjs.map +1 -1
- package/dist/herb-config.esm.js +93 -95
- package/dist/herb-config.esm.js.map +1 -1
- package/dist/index.js.map +1 -0
- package/dist/{src/merge.js → merge.js} +1 -1
- package/dist/merge.js.map +1 -0
- package/dist/types/config-schema.d.ts +19 -0
- package/dist/types/config.d.ts +16 -6
- package/dist/{src/vscode.js → vscode.js} +1 -1
- package/dist/vscode.js.map +1 -0
- package/package.json +3 -3
- package/src/config-schema.ts +11 -0
- package/src/config-template.yml +7 -1
- package/src/config.ts +90 -87
- package/src/merge.ts +1 -1
- package/src/vscode.ts +1 -1
- package/dist/package.json +0 -49
- package/dist/src/config-schema.js.map +0 -1
- package/dist/src/config.js.map +0 -1
- package/dist/src/index.js.map +0 -1
- package/dist/src/merge.js.map +0 -1
- package/dist/src/vscode.js.map +0 -1
- package/dist/tsconfig.tsbuildinfo +0 -1
- package/dist/types/src/config-schema.d.ts +0 -102
- package/dist/types/src/config.d.ts +0 -365
- package/dist/types/src/index.d.ts +0 -5
- package/dist/types/src/merge.d.ts +0 -11
- package/dist/types/src/vscode.d.ts +0 -13
- /package/dist/{src/index.js → index.js} +0 -0
package/dist/herb-config.cjs
CHANGED
|
@@ -1179,7 +1179,7 @@ class Doc {
|
|
|
1179
1179
|
const version$1 = {
|
|
1180
1180
|
major: 4,
|
|
1181
1181
|
minor: 3,
|
|
1182
|
-
patch:
|
|
1182
|
+
patch: 6,
|
|
1183
1183
|
};
|
|
1184
1184
|
|
|
1185
1185
|
const $ZodType = /*@__PURE__*/ $constructor("$ZodType", (inst, def) => {
|
|
@@ -2200,11 +2200,9 @@ const $ZodRecord = /*@__PURE__*/ $constructor("$ZodRecord", (inst, def) => {
|
|
|
2200
2200
|
if (keyResult instanceof Promise) {
|
|
2201
2201
|
throw new Error("Async schemas not supported in object keys currently");
|
|
2202
2202
|
}
|
|
2203
|
-
// Numeric string fallback: if key
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
keyResult.issues.length &&
|
|
2207
|
-
keyResult.issues.some((iss) => iss.code === "invalid_type" && iss.expected === "number");
|
|
2203
|
+
// Numeric string fallback: if key is a numeric string and failed, retry with Number(key)
|
|
2204
|
+
// This handles z.number(), z.literal([1, 2, 3]), and unions containing numeric literals
|
|
2205
|
+
const checkNumericKey = typeof key === "string" && number$1.test(key) && keyResult.issues.length;
|
|
2208
2206
|
if (checkNumericKey) {
|
|
2209
2207
|
const retryResult = def.keyType._zod.run({ value: Number(key), issues: [] }, ctx);
|
|
2210
2208
|
if (retryResult instanceof Promise) {
|
|
@@ -3372,7 +3370,7 @@ function finalize(ctx, schema) {
|
|
|
3372
3370
|
}
|
|
3373
3371
|
}
|
|
3374
3372
|
// When ref was extracted to $defs, remove properties that match the definition
|
|
3375
|
-
if (refSchema.$ref) {
|
|
3373
|
+
if (refSchema.$ref && refSeen.def) {
|
|
3376
3374
|
for (const key in schema) {
|
|
3377
3375
|
if (key === "$ref" || key === "allOf")
|
|
3378
3376
|
continue;
|
|
@@ -4802,9 +4800,18 @@ const FormatterConfigSchema = object({
|
|
|
4802
4800
|
maxLineLength: number().int().positive().optional().describe("Maximum line length before wrapping"),
|
|
4803
4801
|
rewriter: RewriterConfigSchema.describe("Rewriter configuration for pre and post-format transformations"),
|
|
4804
4802
|
}).strict().optional();
|
|
4803
|
+
const ValidatorsConfigSchema = object({
|
|
4804
|
+
security: boolean().optional().describe("Enable or disable the security validator (default: true)"),
|
|
4805
|
+
nesting: boolean().optional().describe("Enable or disable the nesting validator (default: true)"),
|
|
4806
|
+
accessibility: boolean().optional().describe("Enable or disable the accessibility validator (default: true)"),
|
|
4807
|
+
}).strict().optional();
|
|
4808
|
+
const EngineConfigSchema = object({
|
|
4809
|
+
validators: ValidatorsConfigSchema.describe("Per-validator enable/disable configuration"),
|
|
4810
|
+
}).strict().optional();
|
|
4805
4811
|
const HerbConfigSchema = object({
|
|
4806
4812
|
version: string().describe("Configuration file version"),
|
|
4807
4813
|
files: FilesConfigSchema.describe("Top-level file configuration"),
|
|
4814
|
+
engine: EngineConfigSchema.describe("Engine configuration"),
|
|
4808
4815
|
linter: LinterConfigSchema,
|
|
4809
4816
|
formatter: FormatterConfigSchema,
|
|
4810
4817
|
}).strict();
|
|
@@ -4828,7 +4835,7 @@ function deepMerge(target, source) {
|
|
|
4828
4835
|
continue;
|
|
4829
4836
|
}
|
|
4830
4837
|
if (Array.isArray(sourceValue)) {
|
|
4831
|
-
if (key === 'include' && Array.isArray(targetValue)) {
|
|
4838
|
+
if ((key === 'include' || key === 'exclude') && Array.isArray(targetValue)) {
|
|
4832
4839
|
output[key] = [...targetValue, ...sourceValue];
|
|
4833
4840
|
}
|
|
4834
4841
|
else {
|
|
@@ -4846,13 +4853,16 @@ function deepMerge(target, source) {
|
|
|
4846
4853
|
return output;
|
|
4847
4854
|
}
|
|
4848
4855
|
|
|
4849
|
-
var version = "0.
|
|
4856
|
+
var version = "0.9.0";
|
|
4850
4857
|
var packageJson = {
|
|
4851
4858
|
version: version};
|
|
4852
4859
|
|
|
4853
|
-
var configTemplate = "# This file configures Herb for your project and team.\n# Settings here take precedence over individual editor preferences.\n#\n# Herb is a suite of tools for HTML+ERB templates including:\n# - Linter: Validates templates and enforces best practices\n# - Formatter: Auto-formats templates with intelligent indentation\n# - Language Server: Provides IDE support (VS Code, Zed, Neovim, etc.)\n#\n# Website: https://herb-tools.dev\n# Configuration: https://herb-tools.dev/configuration\n# GitHub Repo: https://github.com/marcoroth/herb\n#\n\nversion: 0.
|
|
4860
|
+
var configTemplate = "# This file configures Herb for your project and team.\n# Settings here take precedence over individual editor preferences.\n#\n# Herb is a suite of tools for HTML+ERB templates including:\n# - Linter: Validates templates and enforces best practices\n# - Formatter: Auto-formats templates with intelligent indentation\n# - Language Server: Provides IDE support (VS Code, Zed, Neovim, etc.)\n#\n# Website: https://herb-tools.dev\n# Configuration: https://herb-tools.dev/configuration\n# GitHub Repo: https://github.com/marcoroth/herb\n#\n\nversion: 0.9.0\n\n# files:\n# # Additional patterns beyond the defaults (**.html, **.rhtml, **.html.erb, etc.)\n# include:\n# - '**/*.xml.erb'\n# - 'custom/**/*.html'\n#\n# # Patterns to exclude (can exclude defaults too)\n# exclude:\n# - 'public/**/*'\n# - 'tmp/**/*'\n\n# engine:\n# validators:\n# security: true # default: true\n# nesting: true # default: true\n# accessibility: true # default: true\n\nlinter:\n enabled: true\n\n # # Exit with error code when diagnostics of this severity or higher are present\n # # Valid values: error (default), warning, info, hint\n # failLevel: warning\n\n # # Additional patterns beyond the defaults for linting\n # include:\n # - '**/*.xml.erb'\n #\n # # Patterns to exclude from linting\n # exclude:\n # - 'app/views/admin/**/*'\n\n # rules:\n # erb-no-extra-newline:\n # enabled: false\n #\n # # Rules can have 'include', 'only', and 'exclude' patterns\n # some-rule:\n # # Additional patterns to check (additive, ignored when 'only' is present)\n # include:\n # - 'app/components/**/*'\n # # Don't apply this rule to files matching these patterns\n # exclude:\n # - 'app/views/admin/**/*'\n #\n # another-rule:\n # # Only apply this rule to files matching these patterns (overrides all 'include')\n # only:\n # - 'app/views/**/*'\n # # Exclude still applies even with 'only'\n # exclude:\n # - 'app/views/admin/**/*'\n\nformatter:\n enabled: false\n indentWidth: 2\n maxLineLength: 80\n\n # # Additional patterns beyond the defaults for formatting\n # include:\n # - '**/*.xml.erb'\n #\n # # Patterns to exclude from formatting\n # exclude:\n # - 'app/views/admin/**/*'\n\n # # Rewriters modify templates during formatting\n # rewriter:\n # # Pre-format rewriters (modify AST before formatting)\n # pre:\n # - tailwind-class-sorter\n # # Post-format rewriters (modify formatted output string)\n # post: []\n";
|
|
4861
|
+
|
|
4862
|
+
var defaultsYaml = "files:\n include:\n - \"**/*.herb\"\n - \"**/*.html.erb\"\n - \"**/*.html.herb\"\n - \"**/*.html\"\n - \"**/*.html+*.erb\"\n - \"**/*.rhtml\"\n - \"**/*.turbo_stream.erb\"\n\n exclude:\n - \"coverage/**/*\"\n - \"log/**/*\"\n - \"node_modules/**/*\"\n - \"storage/**/*\"\n - \"tmp/**/*\"\n - \"vendor/**/*\"\n\nengine:\n validators:\n security: true\n nesting: true\n accessibility: true\n\nlinter:\n enabled: true\n rules: {}\n\nformatter:\n enabled: false\n indentWidth: 2\n maxLineLength: 80\n";
|
|
4854
4863
|
|
|
4855
4864
|
const DEFAULT_VERSION = packageJson.version;
|
|
4865
|
+
const PARSED_DEFAULTS = yaml.parse(defaultsYaml);
|
|
4856
4866
|
class Config {
|
|
4857
4867
|
static configPath = ".herb.yml";
|
|
4858
4868
|
static PROJECT_INDICATORS = [
|
|
@@ -4926,8 +4936,9 @@ class Config {
|
|
|
4926
4936
|
}
|
|
4927
4937
|
/**
|
|
4928
4938
|
* Get the files configuration for a specific tool.
|
|
4929
|
-
*
|
|
4930
|
-
* Include
|
|
4939
|
+
* Both include and exclude patterns are additive:
|
|
4940
|
+
* - Include: defaults + files.include + tool.include
|
|
4941
|
+
* - Exclude: defaults + files.exclude + tool.exclude
|
|
4931
4942
|
* @param tool - The tool to get files config for ('linter' or 'formatter')
|
|
4932
4943
|
* @returns The merged files configuration
|
|
4933
4944
|
*/
|
|
@@ -4937,7 +4948,9 @@ class Config {
|
|
|
4937
4948
|
const topLevelInclude = topLevelFiles.include || [];
|
|
4938
4949
|
const toolInclude = toolConfig?.include || [];
|
|
4939
4950
|
const include = [...topLevelInclude, ...toolInclude];
|
|
4940
|
-
const
|
|
4951
|
+
const topLevelExclude = topLevelFiles.exclude || [];
|
|
4952
|
+
const toolExclude = toolConfig?.exclude || [];
|
|
4953
|
+
const exclude = [...topLevelExclude, ...toolExclude];
|
|
4941
4954
|
return {
|
|
4942
4955
|
include,
|
|
4943
4956
|
exclude
|
|
@@ -5021,7 +5034,7 @@ class Config {
|
|
|
5021
5034
|
}
|
|
5022
5035
|
/**
|
|
5023
5036
|
* Check if a tool (linter or formatter) is enabled for a specific file path.
|
|
5024
|
-
* Respects
|
|
5037
|
+
* Respects the tool's enabled state and all exclude patterns (defaults + files.exclude + tool.exclude).
|
|
5025
5038
|
* @param filePath - The file path to check
|
|
5026
5039
|
* @param tool - The tool to check ('linter' or 'formatter')
|
|
5027
5040
|
* @returns true if the tool is enabled for this path
|
|
@@ -5031,8 +5044,8 @@ class Config {
|
|
|
5031
5044
|
if (!isEnabled) {
|
|
5032
5045
|
return false;
|
|
5033
5046
|
}
|
|
5034
|
-
const
|
|
5035
|
-
const excludePatterns =
|
|
5047
|
+
const filesConfig = this.getFilesConfigForTool(tool);
|
|
5048
|
+
const excludePatterns = filesConfig.exclude || [];
|
|
5036
5049
|
return !this.isPathExcluded(filePath, excludePatterns);
|
|
5037
5050
|
}
|
|
5038
5051
|
/**
|
|
@@ -5055,11 +5068,11 @@ class Config {
|
|
|
5055
5068
|
}
|
|
5056
5069
|
/**
|
|
5057
5070
|
* Check if a specific rule is enabled for a specific file path.
|
|
5058
|
-
* Respects
|
|
5071
|
+
* Respects rule.enabled, rule.include, rule.only, and rule.exclude patterns.
|
|
5059
5072
|
*
|
|
5060
5073
|
* Pattern precedence:
|
|
5061
|
-
* - If rule.only
|
|
5062
|
-
* - If rule.only
|
|
5074
|
+
* - If rule.only or rule.include matches the path: bypasses parent excludes (linter.exclude, files.exclude, defaults)
|
|
5075
|
+
* - If no rule.only/include patterns or path doesn't match: respects all parent excludes
|
|
5063
5076
|
* - rule.exclude is always applied regardless of 'only' or 'include'
|
|
5064
5077
|
*
|
|
5065
5078
|
* @param ruleName - The name of the rule to check
|
|
@@ -5067,7 +5080,7 @@ class Config {
|
|
|
5067
5080
|
* @returns true if the rule is enabled for this path
|
|
5068
5081
|
*/
|
|
5069
5082
|
isRuleEnabledForPath(ruleName, filePath) {
|
|
5070
|
-
if (!this.
|
|
5083
|
+
if (!this.isLinterEnabled) {
|
|
5071
5084
|
return false;
|
|
5072
5085
|
}
|
|
5073
5086
|
if (this.isRuleDisabled(ruleName)) {
|
|
@@ -5077,13 +5090,25 @@ class Config {
|
|
|
5077
5090
|
const ruleOnlyPatterns = ruleConfig?.only || [];
|
|
5078
5091
|
const ruleIncludePatterns = ruleConfig?.include || [];
|
|
5079
5092
|
const ruleExcludePatterns = ruleConfig?.exclude || [];
|
|
5093
|
+
let bypassParentExcludes = false;
|
|
5080
5094
|
if (ruleOnlyPatterns.length > 0) {
|
|
5081
|
-
if (
|
|
5095
|
+
if (this.isPathIncluded(filePath, ruleOnlyPatterns)) {
|
|
5096
|
+
bypassParentExcludes = true;
|
|
5097
|
+
}
|
|
5098
|
+
else {
|
|
5082
5099
|
return false;
|
|
5083
5100
|
}
|
|
5084
5101
|
}
|
|
5085
5102
|
else if (ruleIncludePatterns.length > 0) {
|
|
5086
|
-
if (
|
|
5103
|
+
if (this.isPathIncluded(filePath, ruleIncludePatterns)) {
|
|
5104
|
+
bypassParentExcludes = true;
|
|
5105
|
+
}
|
|
5106
|
+
else {
|
|
5107
|
+
return false;
|
|
5108
|
+
}
|
|
5109
|
+
}
|
|
5110
|
+
if (!bypassParentExcludes) {
|
|
5111
|
+
if (!this.isLinterEnabledForPath(filePath)) {
|
|
5087
5112
|
return false;
|
|
5088
5113
|
}
|
|
5089
5114
|
}
|
|
@@ -5420,7 +5445,7 @@ class Config {
|
|
|
5420
5445
|
result.push('');
|
|
5421
5446
|
}
|
|
5422
5447
|
}
|
|
5423
|
-
if (/^ [a-z][\w-]*:/.test(line) && prevLine &&
|
|
5448
|
+
if (/^ [a-z][\w-]*:/.test(line) && prevLine && prevLine.startsWith(' ')) {
|
|
5424
5449
|
result.push('');
|
|
5425
5450
|
}
|
|
5426
5451
|
result.push(line);
|
|
@@ -5574,7 +5599,7 @@ class Config {
|
|
|
5574
5599
|
console.error(`✓ Created default configuration at ${configPath}`);
|
|
5575
5600
|
}
|
|
5576
5601
|
}
|
|
5577
|
-
catch (
|
|
5602
|
+
catch (_error) {
|
|
5578
5603
|
if (!silent) {
|
|
5579
5604
|
console.error(`⚠ Could not create config file at ${configPath}, using defaults in-memory`);
|
|
5580
5605
|
}
|
|
@@ -5663,62 +5688,57 @@ class Config {
|
|
|
5663
5688
|
* Read, parse, and validate config file
|
|
5664
5689
|
*/
|
|
5665
5690
|
static async readAndValidateConfig(configPath, projectRoot, version, exitOnError = false) {
|
|
5691
|
+
const content = await fs.promises.readFile(configPath, "utf8");
|
|
5692
|
+
let parsed;
|
|
5666
5693
|
try {
|
|
5667
|
-
|
|
5668
|
-
|
|
5669
|
-
|
|
5670
|
-
|
|
5694
|
+
parsed = yaml.parse(content);
|
|
5695
|
+
}
|
|
5696
|
+
catch (error) {
|
|
5697
|
+
if (exitOnError) {
|
|
5698
|
+
console.error(`\n✗ Invalid YAML syntax in ${configPath}`);
|
|
5699
|
+
if (error instanceof Error) {
|
|
5700
|
+
console.error(` ${error.message}\n`);
|
|
5701
|
+
}
|
|
5702
|
+
process.exit(1);
|
|
5671
5703
|
}
|
|
5672
|
-
|
|
5704
|
+
else {
|
|
5705
|
+
throw new Error(`Invalid YAML syntax in ${configPath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
5706
|
+
}
|
|
5707
|
+
}
|
|
5708
|
+
if (parsed === null || parsed === undefined) {
|
|
5709
|
+
parsed = {};
|
|
5710
|
+
}
|
|
5711
|
+
if (!parsed.version) {
|
|
5712
|
+
parsed.version = version;
|
|
5713
|
+
}
|
|
5714
|
+
try {
|
|
5715
|
+
HerbConfigSchema.parse(parsed);
|
|
5716
|
+
}
|
|
5717
|
+
catch (error) {
|
|
5718
|
+
if (error instanceof ZodError) {
|
|
5719
|
+
const validationError = fromZodError(error, {
|
|
5720
|
+
prefix: `Configuration errors in ${configPath}`,
|
|
5721
|
+
});
|
|
5673
5722
|
if (exitOnError) {
|
|
5674
|
-
console.error(`\n✗
|
|
5675
|
-
if (error instanceof Error) {
|
|
5676
|
-
console.error(` ${error.message}\n`);
|
|
5677
|
-
}
|
|
5723
|
+
console.error(`\n✗ ${validationError.toString()}\n`);
|
|
5678
5724
|
process.exit(1);
|
|
5679
5725
|
}
|
|
5680
5726
|
else {
|
|
5681
|
-
throw new Error(
|
|
5682
|
-
}
|
|
5683
|
-
}
|
|
5684
|
-
if (parsed === null || parsed === undefined) {
|
|
5685
|
-
parsed = {};
|
|
5686
|
-
}
|
|
5687
|
-
if (!parsed.version) {
|
|
5688
|
-
parsed.version = version;
|
|
5689
|
-
}
|
|
5690
|
-
try {
|
|
5691
|
-
HerbConfigSchema.parse(parsed);
|
|
5692
|
-
}
|
|
5693
|
-
catch (error) {
|
|
5694
|
-
if (error instanceof ZodError) {
|
|
5695
|
-
const validationError = fromZodError(error, {
|
|
5696
|
-
prefix: `Configuration errors in ${configPath}`,
|
|
5697
|
-
});
|
|
5698
|
-
if (exitOnError) {
|
|
5699
|
-
console.error(`\n✗ ${validationError.toString()}\n`);
|
|
5700
|
-
process.exit(1);
|
|
5701
|
-
}
|
|
5702
|
-
else {
|
|
5703
|
-
throw new Error(validationError.toString());
|
|
5704
|
-
}
|
|
5727
|
+
throw new Error(validationError.toString());
|
|
5705
5728
|
}
|
|
5706
|
-
throw error;
|
|
5707
|
-
}
|
|
5708
|
-
if (parsed.version && parsed.version !== version) {
|
|
5709
|
-
console.error(`\n⚠️ Configuration version mismatch in ${configPath}`);
|
|
5710
|
-
console.error(` Config version: ${parsed.version}`);
|
|
5711
|
-
console.error(` Current version: ${version}`);
|
|
5712
|
-
console.error(` Consider updating your .herb.yml file.\n`);
|
|
5713
5729
|
}
|
|
5714
|
-
const defaults = this.getDefaultConfig(version);
|
|
5715
|
-
const resolved = deepMerge(defaults, parsed);
|
|
5716
|
-
resolved.version = version;
|
|
5717
|
-
return new Config(projectRoot, resolved);
|
|
5718
|
-
}
|
|
5719
|
-
catch (error) {
|
|
5720
5730
|
throw error;
|
|
5721
5731
|
}
|
|
5732
|
+
if (parsed.version && parsed.version !== version) {
|
|
5733
|
+
console.error(`\n⚠️ Configuration version mismatch in ${configPath}`);
|
|
5734
|
+
console.error(` Config version: ${parsed.version}`);
|
|
5735
|
+
console.error(` Current version: ${version}`);
|
|
5736
|
+
console.error(` Consider updating your .herb.yml file.\n`);
|
|
5737
|
+
}
|
|
5738
|
+
const defaults = this.getDefaultConfig(version);
|
|
5739
|
+
const resolved = deepMerge(defaults, parsed);
|
|
5740
|
+
resolved.version = version;
|
|
5741
|
+
return new Config(projectRoot, resolved);
|
|
5722
5742
|
}
|
|
5723
5743
|
/**
|
|
5724
5744
|
* Get default configuration object
|
|
@@ -5726,29 +5746,7 @@ class Config {
|
|
|
5726
5746
|
static getDefaultConfig(version = DEFAULT_VERSION) {
|
|
5727
5747
|
return {
|
|
5728
5748
|
version,
|
|
5729
|
-
|
|
5730
|
-
include: [
|
|
5731
|
-
'**/*.html',
|
|
5732
|
-
'**/*.rhtml',
|
|
5733
|
-
'**/*.html.erb',
|
|
5734
|
-
'**/*.html+*.erb',
|
|
5735
|
-
'**/*.turbo_stream.erb'
|
|
5736
|
-
],
|
|
5737
|
-
exclude: [
|
|
5738
|
-
'node_modules/**/*',
|
|
5739
|
-
'vendor/bundle/**/*',
|
|
5740
|
-
'coverage/**/*',
|
|
5741
|
-
]
|
|
5742
|
-
},
|
|
5743
|
-
linter: {
|
|
5744
|
-
enabled: true,
|
|
5745
|
-
rules: {}
|
|
5746
|
-
},
|
|
5747
|
-
formatter: {
|
|
5748
|
-
enabled: false,
|
|
5749
|
-
indentWidth: 2,
|
|
5750
|
-
maxLineLength: 80
|
|
5751
|
-
}
|
|
5749
|
+
...PARSED_DEFAULTS
|
|
5752
5750
|
};
|
|
5753
5751
|
}
|
|
5754
5752
|
}
|
|
@@ -5788,7 +5786,7 @@ function readExtensionsJson(filePath) {
|
|
|
5788
5786
|
}
|
|
5789
5787
|
return parsed;
|
|
5790
5788
|
}
|
|
5791
|
-
catch (
|
|
5789
|
+
catch (_error) {
|
|
5792
5790
|
console.warn(`Warning: Could not parse ${filePath}, creating new file`);
|
|
5793
5791
|
return { recommendations: [] };
|
|
5794
5792
|
}
|