@harness-engineering/linter-gen 0.1.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.
Files changed (44) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +161 -0
  3. package/dist/.tsbuildinfo +1 -0
  4. package/dist/engine/context-builder.d.ts +24 -0
  5. package/dist/engine/context-builder.d.ts.map +1 -0
  6. package/dist/engine/context-builder.js +33 -0
  7. package/dist/engine/context-builder.js.map +1 -0
  8. package/dist/engine/template-loader.d.ts +27 -0
  9. package/dist/engine/template-loader.d.ts.map +1 -0
  10. package/dist/engine/template-loader.js +84 -0
  11. package/dist/engine/template-loader.js.map +1 -0
  12. package/dist/engine/template-renderer.d.ts +17 -0
  13. package/dist/engine/template-renderer.d.ts.map +1 -0
  14. package/dist/engine/template-renderer.js +44 -0
  15. package/dist/engine/template-renderer.js.map +1 -0
  16. package/dist/generator/index-generator.d.ts +5 -0
  17. package/dist/generator/index-generator.d.ts.map +1 -0
  18. package/dist/generator/index-generator.js +36 -0
  19. package/dist/generator/index-generator.js.map +1 -0
  20. package/dist/generator/orchestrator.d.ts +57 -0
  21. package/dist/generator/orchestrator.d.ts.map +1 -0
  22. package/dist/generator/orchestrator.js +106 -0
  23. package/dist/generator/orchestrator.js.map +1 -0
  24. package/dist/generator/rule-generator.d.ts +21 -0
  25. package/dist/generator/rule-generator.d.ts.map +1 -0
  26. package/dist/generator/rule-generator.js +30 -0
  27. package/dist/generator/rule-generator.js.map +1 -0
  28. package/dist/index.d.ts +15 -0
  29. package/dist/index.d.ts.map +1 -0
  30. package/dist/index.js +14 -0
  31. package/dist/index.js.map +1 -0
  32. package/dist/parser/config-parser.d.ts +20 -0
  33. package/dist/parser/config-parser.d.ts.map +1 -0
  34. package/dist/parser/config-parser.js +62 -0
  35. package/dist/parser/config-parser.js.map +1 -0
  36. package/dist/schema/linter-config.d.ts +79 -0
  37. package/dist/schema/linter-config.d.ts.map +1 -0
  38. package/dist/schema/linter-config.js +28 -0
  39. package/dist/schema/linter-config.js.map +1 -0
  40. package/dist/templates/import-restriction.ts.hbs +2 -0
  41. package/dist/templates/templates/boundary-validation.ts.hbs +99 -0
  42. package/dist/templates/templates/dependency-graph.ts.hbs +123 -0
  43. package/dist/templates/templates/import-restriction.ts.hbs +72 -0
  44. package/package.json +50 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index-generator.js","sourceRoot":"","sources":["../../src/generator/index-generator.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,SAAS,WAAW,CAAC,GAAW;IAC9B,OAAO,GAAG,CAAC,OAAO,CAAC,cAAc,EAAE,CAAC,CAAS,EAAE,IAAY,EAAE,EAAE,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC;AACtF,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,aAAa,CAAC,SAAmB;IAC/C,MAAM,OAAO,GAAG,SAAS;SACtB,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE;QACZ,MAAM,KAAK,GAAG,WAAW,CAAC,IAAI,CAAC,CAAC;QAChC,OAAO,UAAU,KAAK,YAAY,IAAI,IAAI,CAAC;IAC7C,CAAC,CAAC;SACD,IAAI,CAAC,IAAI,CAAC,CAAC;IAEd,MAAM,WAAW,GAAG,SAAS;SAC1B,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE;QACZ,MAAM,KAAK,GAAG,WAAW,CAAC,IAAI,CAAC,CAAC;QAChC,OAAO,MAAM,IAAI,MAAM,KAAK,GAAG,CAAC;IAClC,CAAC,CAAC;SACD,IAAI,CAAC,IAAI,CAAC,CAAC;IAEd,MAAM,YAAY,GAAG,SAAS,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAE3D,OAAO;;;EAGP,OAAO;;;EAGP,WAAW;;;WAGF,YAAY;CACtB,CAAC;AACF,CAAC"}
@@ -0,0 +1,57 @@
1
+ import { type ParseError } from '../parser/config-parser.js';
2
+ import { type TemplateLoadError } from '../engine/template-loader.js';
3
+ import type { TemplateError } from '../engine/template-renderer.js';
4
+ export interface GenerateOptions {
5
+ /** Path to harness-linter.yml */
6
+ configPath: string;
7
+ /** Override output directory from config */
8
+ outputDir?: string;
9
+ /** Remove existing files before generating */
10
+ clean?: boolean;
11
+ /** Preview without writing files */
12
+ dryRun?: boolean;
13
+ }
14
+ export interface ValidateOptions {
15
+ configPath: string;
16
+ }
17
+ export type GeneratorError = {
18
+ type: 'parse';
19
+ error: ParseError;
20
+ } | {
21
+ type: 'template';
22
+ error: TemplateLoadError;
23
+ ruleName: string;
24
+ } | {
25
+ type: 'render';
26
+ error: TemplateError;
27
+ ruleName: string;
28
+ } | {
29
+ type: 'write';
30
+ error: Error;
31
+ path: string;
32
+ };
33
+ export type GenerateResult = {
34
+ success: true;
35
+ rulesGenerated: string[];
36
+ outputDir: string;
37
+ dryRun: boolean;
38
+ } | {
39
+ success: false;
40
+ errors: GeneratorError[];
41
+ };
42
+ export type ValidateResult = {
43
+ success: true;
44
+ ruleCount: number;
45
+ } | {
46
+ success: false;
47
+ error: ParseError;
48
+ };
49
+ /**
50
+ * Validate a harness-linter.yml config without generating
51
+ */
52
+ export declare function validate(options: ValidateOptions): Promise<ValidateResult>;
53
+ /**
54
+ * Generate ESLint rules from harness-linter.yml config
55
+ */
56
+ export declare function generate(options: GenerateOptions): Promise<GenerateResult>;
57
+ //# sourceMappingURL=orchestrator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"orchestrator.d.ts","sourceRoot":"","sources":["../../src/generator/orchestrator.ts"],"names":[],"mappings":"AAGA,OAAO,EAAe,KAAK,UAAU,EAAE,MAAM,4BAA4B,CAAC;AAC1E,OAAO,EAAgB,KAAK,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AAGpF,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gCAAgC,CAAC;AAEpE,MAAM,WAAW,eAAe;IAC9B,iCAAiC;IACjC,UAAU,EAAE,MAAM,CAAC;IACnB,4CAA4C;IAC5C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,8CAA8C;IAC9C,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,oCAAoC;IACpC,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,eAAe;IAC9B,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,MAAM,cAAc,GACtB;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,UAAU,CAAA;CAAE,GACpC;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,KAAK,EAAE,iBAAiB,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GAChE;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,KAAK,EAAE,aAAa,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GAC1D;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,KAAK,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAElD,MAAM,MAAM,cAAc,GACtB;IACE,OAAO,EAAE,IAAI,CAAC;IACd,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,OAAO,CAAC;CACjB,GACD;IACE,OAAO,EAAE,KAAK,CAAC;IACf,MAAM,EAAE,cAAc,EAAE,CAAC;CAC1B,CAAC;AAEN,MAAM,MAAM,cAAc,GACtB;IAAE,OAAO,EAAE,IAAI,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,GACpC;IAAE,OAAO,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,UAAU,CAAA;CAAE,CAAC;AAE1C;;GAEG;AACH,wBAAsB,QAAQ,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,cAAc,CAAC,CAMhF;AAED;;GAEG;AACH,wBAAsB,QAAQ,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,cAAc,CAAC,CA6FhF"}
@@ -0,0 +1,106 @@
1
+ // src/generator/orchestrator.ts
2
+ import * as fs from 'fs/promises';
3
+ import * as path from 'path';
4
+ import { parseConfig } from '../parser/config-parser.js';
5
+ import { loadTemplate } from '../engine/template-loader.js';
6
+ import { generateRule } from './rule-generator.js';
7
+ import { generateIndex } from './index-generator.js';
8
+ /**
9
+ * Validate a harness-linter.yml config without generating
10
+ */
11
+ export async function validate(options) {
12
+ const parseResult = await parseConfig(options.configPath);
13
+ if (!parseResult.success) {
14
+ return { success: false, error: parseResult.error };
15
+ }
16
+ return { success: true, ruleCount: parseResult.data.rules.length };
17
+ }
18
+ /**
19
+ * Generate ESLint rules from harness-linter.yml config
20
+ */
21
+ export async function generate(options) {
22
+ const errors = [];
23
+ // Parse config
24
+ const parseResult = await parseConfig(options.configPath);
25
+ if (!parseResult.success) {
26
+ return { success: false, errors: [{ type: 'parse', error: parseResult.error }] };
27
+ }
28
+ const config = parseResult.data;
29
+ const configDir = path.dirname(path.resolve(options.configPath));
30
+ const outputDir = options.outputDir
31
+ ? path.resolve(options.outputDir)
32
+ : path.resolve(configDir, config.output);
33
+ // Clean output directory if requested
34
+ if (options.clean && !options.dryRun) {
35
+ try {
36
+ await fs.rm(outputDir, { recursive: true, force: true });
37
+ }
38
+ catch {
39
+ // Ignore errors - directory might not exist
40
+ }
41
+ }
42
+ // Create output directory
43
+ if (!options.dryRun) {
44
+ await fs.mkdir(outputDir, { recursive: true });
45
+ }
46
+ const generatedRules = [];
47
+ // Generate each rule
48
+ for (const rule of config.rules) {
49
+ // Load template
50
+ const templateResult = await loadTemplate(rule.type, config.templates, configDir);
51
+ if (!templateResult.success) {
52
+ errors.push({
53
+ type: 'template',
54
+ error: templateResult.error,
55
+ ruleName: rule.name,
56
+ });
57
+ continue;
58
+ }
59
+ // Generate rule
60
+ const ruleResult = generateRule(rule, templateResult.source, outputDir, options.configPath);
61
+ if (!ruleResult.success) {
62
+ errors.push({
63
+ type: 'render',
64
+ error: ruleResult.error,
65
+ ruleName: ruleResult.ruleName,
66
+ });
67
+ continue;
68
+ }
69
+ // Write file
70
+ if (!options.dryRun) {
71
+ try {
72
+ await fs.writeFile(ruleResult.rule.outputPath, ruleResult.rule.content, 'utf-8');
73
+ }
74
+ catch (err) {
75
+ errors.push({
76
+ type: 'write',
77
+ error: err,
78
+ path: ruleResult.rule.outputPath,
79
+ });
80
+ continue;
81
+ }
82
+ }
83
+ generatedRules.push(rule.name);
84
+ }
85
+ // Generate index file
86
+ if (generatedRules.length > 0 && !options.dryRun) {
87
+ const indexContent = generateIndex(generatedRules);
88
+ const indexPath = path.join(outputDir, 'index.ts');
89
+ try {
90
+ await fs.writeFile(indexPath, indexContent, 'utf-8');
91
+ }
92
+ catch (err) {
93
+ errors.push({ type: 'write', error: err, path: indexPath });
94
+ }
95
+ }
96
+ if (errors.length > 0) {
97
+ return { success: false, errors };
98
+ }
99
+ return {
100
+ success: true,
101
+ rulesGenerated: generatedRules,
102
+ outputDir,
103
+ dryRun: options.dryRun ?? false,
104
+ };
105
+ }
106
+ //# sourceMappingURL=orchestrator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"orchestrator.js","sourceRoot":"","sources":["../../src/generator/orchestrator.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ import type { RuleConfig } from '../schema/linter-config.js';
2
+ import type { TemplateSource } from '../engine/template-loader.js';
3
+ import { TemplateError } from '../engine/template-renderer.js';
4
+ export interface GeneratedRule {
5
+ name: string;
6
+ outputPath: string;
7
+ content: string;
8
+ }
9
+ export type GenerateRuleResult = {
10
+ success: true;
11
+ rule: GeneratedRule;
12
+ } | {
13
+ success: false;
14
+ error: TemplateError;
15
+ ruleName: string;
16
+ };
17
+ /**
18
+ * Generate a single ESLint rule file from config and template
19
+ */
20
+ export declare function generateRule(rule: RuleConfig, template: TemplateSource, outputDir: string, configPath: string): GenerateRuleResult;
21
+ //# sourceMappingURL=rule-generator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rule-generator.d.ts","sourceRoot":"","sources":["../../src/generator/rule-generator.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAC;AAC7D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAEnE,OAAO,EAAkB,aAAa,EAAE,MAAM,gCAAgC,CAAC;AAE/E,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,MAAM,kBAAkB,GAC1B;IAAE,OAAO,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,aAAa,CAAA;CAAE,GACtC;IAAE,OAAO,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,aAAa,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC;AAE/D;;GAEG;AACH,wBAAgB,YAAY,CAC1B,IAAI,EAAE,UAAU,EAChB,QAAQ,EAAE,cAAc,EACxB,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,GACjB,kBAAkB,CAyBpB"}
@@ -0,0 +1,30 @@
1
+ import * as path from 'path';
2
+ import { buildRuleContext } from '../engine/context-builder.js';
3
+ import { renderTemplate } from '../engine/template-renderer.js';
4
+ /**
5
+ * Generate a single ESLint rule file from config and template
6
+ */
7
+ export function generateRule(rule, template, outputDir, configPath) {
8
+ // Build template context
9
+ const context = buildRuleContext(rule, configPath);
10
+ // Render template
11
+ const renderResult = renderTemplate(template.content, context);
12
+ if (!renderResult.success) {
13
+ return {
14
+ success: false,
15
+ error: renderResult.error,
16
+ ruleName: rule.name,
17
+ };
18
+ }
19
+ // Construct output path
20
+ const outputPath = path.join(outputDir, `${rule.name}.ts`);
21
+ return {
22
+ success: true,
23
+ rule: {
24
+ name: rule.name,
25
+ outputPath,
26
+ content: renderResult.output,
27
+ },
28
+ };
29
+ }
30
+ //# sourceMappingURL=rule-generator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rule-generator.js","sourceRoot":"","sources":["../../src/generator/rule-generator.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAG7B,OAAO,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AAChE,OAAO,EAAE,cAAc,EAAiB,MAAM,gCAAgC,CAAC;AAY/E;;GAEG;AACH,MAAM,UAAU,YAAY,CAC1B,IAAgB,EAChB,QAAwB,EACxB,SAAiB,EACjB,UAAkB;IAElB,yBAAyB;IACzB,MAAM,OAAO,GAAG,gBAAgB,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC;IAEnD,kBAAkB;IAClB,MAAM,YAAY,GAAG,cAAc,CAAC,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;IAC/D,IAAI,CAAC,YAAY,CAAC,OAAO,EAAE,CAAC;QAC1B,OAAO;YACL,OAAO,EAAE,KAAK;YACd,KAAK,EAAE,YAAY,CAAC,KAAK;YACzB,QAAQ,EAAE,IAAI,CAAC,IAAI;SACpB,CAAC;IACJ,CAAC;IAED,wBAAwB;IACxB,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,GAAG,IAAI,CAAC,IAAI,KAAK,CAAC,CAAC;IAE3D,OAAO;QACL,OAAO,EAAE,IAAI;QACb,IAAI,EAAE;YACJ,IAAI,EAAE,IAAI,CAAC,IAAI;YACf,UAAU;YACV,OAAO,EAAE,YAAY,CAAC,MAAM;SAC7B;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * @harness-engineering/linter-gen
3
+ *
4
+ * Generate ESLint rules from YAML configuration
5
+ */
6
+ export { generate, validate } from './generator/orchestrator.js';
7
+ export type { GenerateOptions, ValidateOptions, GenerateResult, ValidateResult, GeneratorError, } from './generator/orchestrator.js';
8
+ export { LinterConfigSchema, RuleConfigSchema } from './schema/linter-config.js';
9
+ export type { LinterConfig, RuleConfig } from './schema/linter-config.js';
10
+ export type { RuleContext } from './engine/context-builder.js';
11
+ export type { TemplateSource, TemplateSourceType } from './engine/template-loader.js';
12
+ export { ParseError } from './parser/config-parser.js';
13
+ export { TemplateLoadError } from './engine/template-loader.js';
14
+ export { TemplateError } from './engine/template-renderer.js';
15
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,6BAA6B,CAAC;AACjE,YAAY,EACV,eAAe,EACf,eAAe,EACf,cAAc,EACd,cAAc,EACd,cAAc,GACf,MAAM,6BAA6B,CAAC;AAGrC,OAAO,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AACjF,YAAY,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AAG1E,YAAY,EAAE,WAAW,EAAE,MAAM,6BAA6B,CAAC;AAC/D,YAAY,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,6BAA6B,CAAC;AAGtF,OAAO,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AACvD,OAAO,EAAE,iBAAiB,EAAE,MAAM,6BAA6B,CAAC;AAChE,OAAO,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,14 @@
1
+ /**
2
+ * @harness-engineering/linter-gen
3
+ *
4
+ * Generate ESLint rules from YAML configuration
5
+ */
6
+ // Main API
7
+ export { generate, validate } from './generator/orchestrator.js';
8
+ // Schema types
9
+ export { LinterConfigSchema, RuleConfigSchema } from './schema/linter-config.js';
10
+ // Error types
11
+ export { ParseError } from './parser/config-parser.js';
12
+ export { TemplateLoadError } from './engine/template-loader.js';
13
+ export { TemplateError } from './engine/template-renderer.js';
14
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,WAAW;AACX,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,6BAA6B,CAAC;AASjE,eAAe;AACf,OAAO,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAOjF,cAAc;AACd,OAAO,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AACvD,OAAO,EAAE,iBAAiB,EAAE,MAAM,6BAA6B,CAAC;AAChE,OAAO,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAC"}
@@ -0,0 +1,20 @@
1
+ import { type LinterConfig } from '../schema/linter-config.js';
2
+ export type ParseErrorCode = 'FILE_NOT_FOUND' | 'FILE_READ_ERROR' | 'YAML_PARSE_ERROR' | 'VALIDATION_ERROR';
3
+ export declare class ParseError extends Error {
4
+ readonly code: ParseErrorCode;
5
+ readonly cause?: unknown | undefined;
6
+ constructor(message: string, code: ParseErrorCode, cause?: unknown | undefined);
7
+ }
8
+ export type ParseResult = {
9
+ success: true;
10
+ data: LinterConfig;
11
+ configPath: string;
12
+ } | {
13
+ success: false;
14
+ error: ParseError;
15
+ };
16
+ /**
17
+ * Parse and validate a harness-linter.yml config file
18
+ */
19
+ export declare function parseConfig(configPath: string): Promise<ParseResult>;
20
+ //# sourceMappingURL=config-parser.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config-parser.d.ts","sourceRoot":"","sources":["../../src/parser/config-parser.ts"],"names":[],"mappings":"AAGA,OAAO,EAAsB,KAAK,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAEnF,MAAM,MAAM,cAAc,GACtB,gBAAgB,GAChB,iBAAiB,GACjB,kBAAkB,GAClB,kBAAkB,CAAC;AAEvB,qBAAa,UAAW,SAAQ,KAAK;aAGjB,IAAI,EAAE,cAAc;aACpB,KAAK,CAAC,EAAE,OAAO;gBAF/B,OAAO,EAAE,MAAM,EACC,IAAI,EAAE,cAAc,EACpB,KAAK,CAAC,EAAE,OAAO,YAAA;CAKlC;AAED,MAAM,MAAM,WAAW,GACnB;IAAE,OAAO,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,YAAY,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,GACzD;IAAE,OAAO,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,UAAU,CAAA;CAAE,CAAC;AAE1C;;GAEG;AACH,wBAAsB,WAAW,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAgD1E"}
@@ -0,0 +1,62 @@
1
+ /// <reference types="node" />
2
+ import * as fs from 'fs/promises';
3
+ import * as yaml from 'yaml';
4
+ import { LinterConfigSchema } from '../schema/linter-config.js';
5
+ export class ParseError extends Error {
6
+ code;
7
+ cause;
8
+ constructor(message, code, cause) {
9
+ super(message);
10
+ this.code = code;
11
+ this.cause = cause;
12
+ this.name = 'ParseError';
13
+ }
14
+ }
15
+ /**
16
+ * Parse and validate a harness-linter.yml config file
17
+ */
18
+ export async function parseConfig(configPath) {
19
+ // Read file
20
+ let content;
21
+ try {
22
+ content = await fs.readFile(configPath, 'utf-8');
23
+ }
24
+ catch (err) {
25
+ if (err.code === 'ENOENT') {
26
+ return {
27
+ success: false,
28
+ error: new ParseError(`Config file not found: ${configPath}`, 'FILE_NOT_FOUND', err),
29
+ };
30
+ }
31
+ return {
32
+ success: false,
33
+ error: new ParseError(`Failed to read config file: ${configPath}`, 'FILE_READ_ERROR', err),
34
+ };
35
+ }
36
+ // Parse YAML
37
+ let parsed;
38
+ try {
39
+ parsed = yaml.parse(content);
40
+ }
41
+ catch (err) {
42
+ return {
43
+ success: false,
44
+ error: new ParseError(`Invalid YAML syntax in ${configPath}: ${err.message}`, 'YAML_PARSE_ERROR', err),
45
+ };
46
+ }
47
+ // Validate with Zod
48
+ const result = LinterConfigSchema.safeParse(parsed);
49
+ if (!result.success) {
50
+ const issues = result.error.issues.map((i) => `${i.path.join('.')}: ${i.message}`).join('; ');
51
+ return {
52
+ success: false,
53
+ error: new ParseError(`Invalid config: ${issues}`, 'VALIDATION_ERROR', result.error),
54
+ };
55
+ }
56
+ return {
57
+ success: true,
58
+ data: result.data,
59
+ configPath,
60
+ };
61
+ }
62
+ //# sourceMappingURL=config-parser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config-parser.js","sourceRoot":"","sources":["../../src/parser/config-parser.ts"],"names":[],"mappings":"AAAA,8BAA8B;AAC9B,OAAO,KAAK,EAAE,MAAM,aAAa,CAAC;AAClC,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAC7B,OAAO,EAAE,kBAAkB,EAAqB,MAAM,4BAA4B,CAAC;AAQnF,MAAM,OAAO,UAAW,SAAQ,KAAK;IAGjB;IACA;IAHlB,YACE,OAAe,EACC,IAAoB,EACpB,KAAe;QAE/B,KAAK,CAAC,OAAO,CAAC,CAAC;QAHC,SAAI,GAAJ,IAAI,CAAgB;QACpB,UAAK,GAAL,KAAK,CAAU;QAG/B,IAAI,CAAC,IAAI,GAAG,YAAY,CAAC;IAC3B,CAAC;CACF;AAMD;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,UAAkB;IAClD,YAAY;IACZ,IAAI,OAAe,CAAC;IACpB,IAAI,CAAC;QACH,OAAO,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;IACnD,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAK,GAAyB,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;YACjD,OAAO;gBACL,OAAO,EAAE,KAAK;gBACd,KAAK,EAAE,IAAI,UAAU,CAAC,0BAA0B,UAAU,EAAE,EAAE,gBAAgB,EAAE,GAAG,CAAC;aACrF,CAAC;QACJ,CAAC;QACD,OAAO;YACL,OAAO,EAAE,KAAK;YACd,KAAK,EAAE,IAAI,UAAU,CAAC,+BAA+B,UAAU,EAAE,EAAE,iBAAiB,EAAE,GAAG,CAAC;SAC3F,CAAC;IACJ,CAAC;IAED,aAAa;IACb,IAAI,MAAe,CAAC;IACpB,IAAI,CAAC;QACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IAC/B,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO;YACL,OAAO,EAAE,KAAK;YACd,KAAK,EAAE,IAAI,UAAU,CACnB,0BAA0B,UAAU,KAAM,GAAa,CAAC,OAAO,EAAE,EACjE,kBAAkB,EAClB,GAAG,CACJ;SACF,CAAC;IACJ,CAAC;IAED,oBAAoB;IACpB,MAAM,MAAM,GAAG,kBAAkB,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;IACpD,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;QACpB,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC9F,OAAO;YACL,OAAO,EAAE,KAAK;YACd,KAAK,EAAE,IAAI,UAAU,CAAC,mBAAmB,MAAM,EAAE,EAAE,kBAAkB,EAAE,MAAM,CAAC,KAAK,CAAC;SACrF,CAAC;IACJ,CAAC;IAED,OAAO;QACL,OAAO,EAAE,IAAI;QACb,IAAI,EAAE,MAAM,CAAC,IAAI;QACjB,UAAU;KACX,CAAC;AACJ,CAAC"}
@@ -0,0 +1,79 @@
1
+ import { z } from 'zod';
2
+ /**
3
+ * Schema for a single rule configuration in harness-linter.yml
4
+ */
5
+ export declare const RuleConfigSchema: z.ZodObject<{
6
+ /** Rule name in kebab-case (e.g., 'no-ui-in-services') */
7
+ name: z.ZodString;
8
+ /** Rule type - determines which template to use */
9
+ type: z.ZodString;
10
+ /** ESLint severity level */
11
+ severity: z.ZodDefault<z.ZodEnum<["error", "warn", "off"]>>;
12
+ /** Template-specific configuration */
13
+ config: z.ZodRecord<z.ZodString, z.ZodUnknown>;
14
+ }, "strip", z.ZodTypeAny, {
15
+ name: string;
16
+ type: string;
17
+ severity: "error" | "warn" | "off";
18
+ config: Record<string, unknown>;
19
+ }, {
20
+ name: string;
21
+ type: string;
22
+ config: Record<string, unknown>;
23
+ severity?: "error" | "warn" | "off" | undefined;
24
+ }>;
25
+ export type RuleConfig = z.infer<typeof RuleConfigSchema>;
26
+ /**
27
+ * Schema for the complete harness-linter.yml configuration
28
+ */
29
+ export declare const LinterConfigSchema: z.ZodObject<{
30
+ /** Config version - currently only 1 is supported */
31
+ version: z.ZodLiteral<1>;
32
+ /** Output directory for generated rules */
33
+ output: z.ZodString;
34
+ /** Optional explicit template path mappings (type → path) */
35
+ templates: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
36
+ /** Rules to generate */
37
+ rules: z.ZodArray<z.ZodObject<{
38
+ /** Rule name in kebab-case (e.g., 'no-ui-in-services') */
39
+ name: z.ZodString;
40
+ /** Rule type - determines which template to use */
41
+ type: z.ZodString;
42
+ /** ESLint severity level */
43
+ severity: z.ZodDefault<z.ZodEnum<["error", "warn", "off"]>>;
44
+ /** Template-specific configuration */
45
+ config: z.ZodRecord<z.ZodString, z.ZodUnknown>;
46
+ }, "strip", z.ZodTypeAny, {
47
+ name: string;
48
+ type: string;
49
+ severity: "error" | "warn" | "off";
50
+ config: Record<string, unknown>;
51
+ }, {
52
+ name: string;
53
+ type: string;
54
+ config: Record<string, unknown>;
55
+ severity?: "error" | "warn" | "off" | undefined;
56
+ }>, "many">;
57
+ }, "strip", z.ZodTypeAny, {
58
+ version: 1;
59
+ output: string;
60
+ rules: {
61
+ name: string;
62
+ type: string;
63
+ severity: "error" | "warn" | "off";
64
+ config: Record<string, unknown>;
65
+ }[];
66
+ templates?: Record<string, string> | undefined;
67
+ }, {
68
+ version: 1;
69
+ output: string;
70
+ rules: {
71
+ name: string;
72
+ type: string;
73
+ config: Record<string, unknown>;
74
+ severity?: "error" | "warn" | "off" | undefined;
75
+ }[];
76
+ templates?: Record<string, string> | undefined;
77
+ }>;
78
+ export type LinterConfig = z.infer<typeof LinterConfigSchema>;
79
+ //# sourceMappingURL=linter-config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"linter-config.d.ts","sourceRoot":"","sources":["../../src/schema/linter-config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;GAEG;AACH,eAAO,MAAM,gBAAgB;IAC3B,0DAA0D;;IAE1D,mDAAmD;;IAEnD,4BAA4B;;IAE5B,sCAAsC;;;;;;;;;;;;EAEtC,CAAC;AAEH,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAE1D;;GAEG;AACH,eAAO,MAAM,kBAAkB;IAC7B,qDAAqD;;IAErD,2CAA2C;;IAE3C,6DAA6D;;IAE7D,wBAAwB;;QAtBxB,0DAA0D;;QAE1D,mDAAmD;;QAEnD,4BAA4B;;QAE5B,sCAAsC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAkBtC,CAAC;AAEH,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC"}
@@ -0,0 +1,28 @@
1
+ import { z } from 'zod';
2
+ /**
3
+ * Schema for a single rule configuration in harness-linter.yml
4
+ */
5
+ export const RuleConfigSchema = z.object({
6
+ /** Rule name in kebab-case (e.g., 'no-ui-in-services') */
7
+ name: z.string().regex(/^[a-z][a-z0-9]*(-[a-z0-9]+)*$/, 'Rule name must be kebab-case'),
8
+ /** Rule type - determines which template to use */
9
+ type: z.string().min(1),
10
+ /** ESLint severity level */
11
+ severity: z.enum(['error', 'warn', 'off']).default('error'),
12
+ /** Template-specific configuration */
13
+ config: z.record(z.unknown()),
14
+ });
15
+ /**
16
+ * Schema for the complete harness-linter.yml configuration
17
+ */
18
+ export const LinterConfigSchema = z.object({
19
+ /** Config version - currently only 1 is supported */
20
+ version: z.literal(1),
21
+ /** Output directory for generated rules */
22
+ output: z.string().min(1),
23
+ /** Optional explicit template path mappings (type → path) */
24
+ templates: z.record(z.string()).optional(),
25
+ /** Rules to generate */
26
+ rules: z.array(RuleConfigSchema).min(1, 'At least one rule is required'),
27
+ });
28
+ //# sourceMappingURL=linter-config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"linter-config.js","sourceRoot":"","sources":["../../src/schema/linter-config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;GAEG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,CAAC,MAAM,CAAC;IACvC,0DAA0D;IAC1D,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC,+BAA+B,EAAE,8BAA8B,CAAC;IACvF,mDAAmD;IACnD,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IACvB,4BAA4B;IAC5B,QAAQ,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC;IAC3D,sCAAsC;IACtC,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC;CAC9B,CAAC,CAAC;AAIH;;GAEG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,CAAC,MAAM,CAAC;IACzC,qDAAqD;IACrD,OAAO,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC;IACrB,2CAA2C;IAC3C,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IACzB,6DAA6D;IAC7D,SAAS,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,QAAQ,EAAE;IAC1C,wBAAwB;IACxB,KAAK,EAAE,CAAC,CAAC,KAAK,CAAC,gBAAgB,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE,+BAA+B,CAAC;CACzE,CAAC,CAAC"}
@@ -0,0 +1,2 @@
1
+ // Import restriction rule
2
+ export const ruleName = '{{name}}';
@@ -0,0 +1,99 @@
1
+ // Generated by @harness-engineering/linter-gen
2
+ // Do not edit manually - regenerate from harness-linter.yml
3
+ // Config: {{meta.configPath}}
4
+
5
+ import { ESLintUtils, type TSESTree } from '@typescript-eslint/utils';
6
+ import { minimatch } from 'minimatch';
7
+
8
+ const createRule = ESLintUtils.RuleCreator(
9
+ () => 'https://github.com/harness-engineering/linter-gen'
10
+ );
11
+
12
+ type MessageIds = 'missingSchema';
13
+
14
+ const FILE_PATTERN = '{{{json config.pattern}}}';
15
+ const REQUIRE_ZOD_SCHEMA = {{config.requireZodSchema}};
16
+ const MESSAGE = '{{{config.message}}}';
17
+
18
+ /**
19
+ * Normalize path separators to forward slashes
20
+ */
21
+ function normalizePath(filePath: string): string {
22
+ return filePath.replace(/\\/g, '/');
23
+ }
24
+
25
+ /**
26
+ * Check if a path matches a glob pattern
27
+ */
28
+ function matchesPattern(filePath: string, pattern: string): boolean {
29
+ const normalized = normalizePath(filePath);
30
+ return minimatch(normalized, pattern, { matchBase: true });
31
+ }
32
+
33
+ export default createRule<[], MessageIds>({
34
+ name: '{{name}}',
35
+ meta: {
36
+ type: 'problem',
37
+ docs: {
38
+ description: MESSAGE,
39
+ },
40
+ messages: {
41
+ missingSchema: MESSAGE,
42
+ },
43
+ schema: [],
44
+ },
45
+ defaultOptions: [],
46
+ create(context) {
47
+ const filePath = normalizePath(context.filename);
48
+
49
+ // Only apply to files matching pattern
50
+ if (!matchesPattern(filePath, FILE_PATTERN)) {
51
+ return {};
52
+ }
53
+
54
+ if (!REQUIRE_ZOD_SCHEMA) {
55
+ return {};
56
+ }
57
+
58
+ let hasZodSchema = false;
59
+ let hasExport = false;
60
+
61
+ return {
62
+ // Check for Zod imports
63
+ ImportDeclaration(node: TSESTree.ImportDeclaration) {
64
+ if (node.source.value === 'zod') {
65
+ hasZodSchema = true;
66
+ }
67
+ },
68
+
69
+ // Check for z.object, z.string, etc.
70
+ CallExpression(node: TSESTree.CallExpression) {
71
+ if (
72
+ node.callee.type === 'MemberExpression' &&
73
+ node.callee.object.type === 'Identifier' &&
74
+ node.callee.object.name === 'z'
75
+ ) {
76
+ hasZodSchema = true;
77
+ }
78
+ },
79
+
80
+ // Track exports
81
+ ExportNamedDeclaration() {
82
+ hasExport = true;
83
+ },
84
+ ExportDefaultDeclaration() {
85
+ hasExport = true;
86
+ },
87
+
88
+ // Check at end of file
89
+ 'Program:exit'(node: TSESTree.Program) {
90
+ if (hasExport && !hasZodSchema) {
91
+ context.report({
92
+ node,
93
+ messageId: 'missingSchema',
94
+ });
95
+ }
96
+ },
97
+ };
98
+ },
99
+ });