@harness-engineering/eslint-plugin 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 (63) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +91 -0
  3. package/dist/configs/index.d.ts +5 -0
  4. package/dist/configs/index.d.ts.map +1 -0
  5. package/dist/configs/index.js +8 -0
  6. package/dist/configs/index.js.map +1 -0
  7. package/dist/configs/recommended.d.ts +4 -0
  8. package/dist/configs/recommended.d.ts.map +1 -0
  9. package/dist/configs/recommended.js +16 -0
  10. package/dist/configs/recommended.js.map +1 -0
  11. package/dist/configs/strict.d.ts +4 -0
  12. package/dist/configs/strict.d.ts.map +1 -0
  13. package/dist/configs/strict.js +16 -0
  14. package/dist/configs/strict.js.map +1 -0
  15. package/dist/index.d.ts +134 -0
  16. package/dist/index.d.ts.map +1 -0
  17. package/dist/index.js +46 -0
  18. package/dist/index.js.map +1 -0
  19. package/dist/rules/enforce-doc-exports.d.ts +12 -0
  20. package/dist/rules/enforce-doc-exports.d.ts.map +1 -0
  21. package/dist/rules/enforce-doc-exports.js +78 -0
  22. package/dist/rules/enforce-doc-exports.js.map +1 -0
  23. package/dist/rules/index.d.ts +21 -0
  24. package/dist/rules/index.d.ts.map +1 -0
  25. package/dist/rules/index.js +14 -0
  26. package/dist/rules/index.js.map +1 -0
  27. package/dist/rules/no-circular-deps.d.ts +20 -0
  28. package/dist/rules/no-circular-deps.d.ts.map +1 -0
  29. package/dist/rules/no-circular-deps.js +110 -0
  30. package/dist/rules/no-circular-deps.js.map +1 -0
  31. package/dist/rules/no-forbidden-imports.d.ts +6 -0
  32. package/dist/rules/no-forbidden-imports.d.ts.map +1 -0
  33. package/dist/rules/no-forbidden-imports.js +58 -0
  34. package/dist/rules/no-forbidden-imports.js.map +1 -0
  35. package/dist/rules/no-layer-violation.d.ts +6 -0
  36. package/dist/rules/no-layer-violation.d.ts.map +1 -0
  37. package/dist/rules/no-layer-violation.js +62 -0
  38. package/dist/rules/no-layer-violation.js.map +1 -0
  39. package/dist/rules/require-boundary-schema.d.ts +6 -0
  40. package/dist/rules/require-boundary-schema.d.ts.map +1 -0
  41. package/dist/rules/require-boundary-schema.js +53 -0
  42. package/dist/rules/require-boundary-schema.js.map +1 -0
  43. package/dist/utils/ast-helpers.d.ts +14 -0
  44. package/dist/utils/ast-helpers.d.ts.map +1 -0
  45. package/dist/utils/ast-helpers.js +94 -0
  46. package/dist/utils/ast-helpers.js.map +1 -0
  47. package/dist/utils/config-loader.d.ts +10 -0
  48. package/dist/utils/config-loader.d.ts.map +1 -0
  49. package/dist/utils/config-loader.js +56 -0
  50. package/dist/utils/config-loader.js.map +1 -0
  51. package/dist/utils/index.d.ts +5 -0
  52. package/dist/utils/index.d.ts.map +1 -0
  53. package/dist/utils/index.js +6 -0
  54. package/dist/utils/index.js.map +1 -0
  55. package/dist/utils/path-utils.d.ts +24 -0
  56. package/dist/utils/path-utils.d.ts.map +1 -0
  57. package/dist/utils/path-utils.js +62 -0
  58. package/dist/utils/path-utils.js.map +1 -0
  59. package/dist/utils/schema.d.ts +117 -0
  60. package/dist/utils/schema.d.ts.map +1 -0
  61. package/dist/utils/schema.js +34 -0
  62. package/dist/utils/schema.js.map +1 -0
  63. package/package.json +62 -0
@@ -0,0 +1,110 @@
1
+ // src/rules/no-circular-deps.ts
2
+ import { ESLintUtils } from '@typescript-eslint/utils';
3
+ import * as path from 'path';
4
+ const createRule = ESLintUtils.RuleCreator((name) => `https://github.com/harness-engineering/eslint-plugin/blob/main/docs/rules/${name}.md`);
5
+ // Module-level import graph (persists per lint run)
6
+ const importGraph = new Map();
7
+ /**
8
+ * Clear the import graph (for testing)
9
+ */
10
+ export function clearImportGraph() {
11
+ importGraph.clear();
12
+ }
13
+ /**
14
+ * Add an edge to the import graph (exported for testing)
15
+ */
16
+ export function addEdge(from, to) {
17
+ if (!importGraph.has(from)) {
18
+ importGraph.set(from, new Set());
19
+ }
20
+ importGraph.get(from).add(to);
21
+ }
22
+ /**
23
+ * Check if adding edge from -> to creates a cycle
24
+ * Returns the cycle path if found, null otherwise
25
+ * Exported for testing
26
+ */
27
+ export function detectCycle(from, to) {
28
+ // DFS from 'to' back to 'from'
29
+ const visited = new Set();
30
+ const cyclePath = [to];
31
+ function dfs(current) {
32
+ if (current === from) {
33
+ return true; // Found cycle
34
+ }
35
+ if (visited.has(current)) {
36
+ return false;
37
+ }
38
+ visited.add(current);
39
+ const deps = importGraph.get(current);
40
+ if (deps) {
41
+ for (const dep of deps) {
42
+ cyclePath.push(dep);
43
+ if (dfs(dep)) {
44
+ return true;
45
+ }
46
+ cyclePath.pop();
47
+ }
48
+ }
49
+ return false;
50
+ }
51
+ if (dfs(to)) {
52
+ return [from, ...cyclePath];
53
+ }
54
+ return null;
55
+ }
56
+ /**
57
+ * Normalize file path to project-relative
58
+ */
59
+ function normalizePath(filePath) {
60
+ // Extract path from /project/src/... or similar
61
+ const srcIndex = filePath.indexOf('/src/');
62
+ if (srcIndex !== -1) {
63
+ return filePath.slice(srcIndex + 1);
64
+ }
65
+ return path.basename(filePath);
66
+ }
67
+ export default createRule({
68
+ name: 'no-circular-deps',
69
+ meta: {
70
+ type: 'problem',
71
+ docs: {
72
+ description: 'Detect circular import dependencies',
73
+ },
74
+ messages: {
75
+ circularDep: 'Circular dependency detected: {{cycle}}',
76
+ },
77
+ schema: [],
78
+ },
79
+ defaultOptions: [],
80
+ create(context) {
81
+ const currentFile = normalizePath(context.filename);
82
+ return {
83
+ ImportDeclaration(node) {
84
+ const importPath = node.source.value;
85
+ // Skip external imports
86
+ if (!importPath.startsWith('.')) {
87
+ return;
88
+ }
89
+ // Resolve import to normalized path
90
+ const importingDir = path.dirname(context.filename);
91
+ const resolvedPath = path.resolve(importingDir, importPath);
92
+ const normalizedImport = normalizePath(resolvedPath);
93
+ // Check for cycle before adding edge
94
+ const cycle = detectCycle(currentFile, normalizedImport);
95
+ if (cycle) {
96
+ context.report({
97
+ node,
98
+ messageId: 'circularDep',
99
+ data: {
100
+ cycle: cycle.map((f) => path.basename(f)).join(' → '),
101
+ },
102
+ });
103
+ }
104
+ // Add edge to graph
105
+ addEdge(currentFile, normalizedImport);
106
+ },
107
+ };
108
+ },
109
+ });
110
+ //# sourceMappingURL=no-circular-deps.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"no-circular-deps.js","sourceRoot":"","sources":["../../src/rules/no-circular-deps.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ import { ESLintUtils } from '@typescript-eslint/utils';
2
+ declare const _default: ESLintUtils.RuleModule<"forbiddenImport", [], unknown, ESLintUtils.RuleListener> & {
3
+ name: string;
4
+ };
5
+ export default _default;
6
+ //# sourceMappingURL=no-forbidden-imports.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"no-forbidden-imports.d.ts","sourceRoot":"","sources":["../../src/rules/no-forbidden-imports.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAiB,MAAM,0BAA0B,CAAC;;;;AAUtE,wBA6DG"}
@@ -0,0 +1,58 @@
1
+ // src/rules/no-forbidden-imports.ts
2
+ import { ESLintUtils } from '@typescript-eslint/utils';
3
+ import { getConfig } from '../utils/config-loader';
4
+ import { matchesPattern, resolveImportPath, normalizePath } from '../utils/path-utils';
5
+ const createRule = ESLintUtils.RuleCreator((name) => `https://github.com/harness-engineering/eslint-plugin/blob/main/docs/rules/${name}.md`);
6
+ export default createRule({
7
+ name: 'no-forbidden-imports',
8
+ meta: {
9
+ type: 'problem',
10
+ docs: {
11
+ description: 'Block forbidden imports based on configurable patterns',
12
+ },
13
+ messages: {
14
+ forbiddenImport: '{{message}}',
15
+ },
16
+ schema: [],
17
+ },
18
+ defaultOptions: [],
19
+ create(context) {
20
+ const config = getConfig(context.filename);
21
+ if (!config?.forbiddenImports?.length) {
22
+ return {}; // No-op if no config
23
+ }
24
+ // Get file path relative to project
25
+ const filePath = normalizePath(context.filename);
26
+ // Find matching rules for this file
27
+ const applicableRules = config.forbiddenImports.filter((rule) => matchesPattern(filePath, rule.from));
28
+ if (applicableRules.length === 0) {
29
+ return {}; // No rules apply to this file
30
+ }
31
+ return {
32
+ ImportDeclaration(node) {
33
+ const importPath = node.source.value;
34
+ const resolvedImport = resolveImportPath(importPath, context.filename);
35
+ for (const rule of applicableRules) {
36
+ for (const disallowed of rule.disallow) {
37
+ // Check if import matches disallow pattern
38
+ const isMatch = importPath === disallowed ||
39
+ matchesPattern(resolvedImport, disallowed) ||
40
+ matchesPattern(importPath, disallowed);
41
+ if (isMatch) {
42
+ context.report({
43
+ node,
44
+ messageId: 'forbiddenImport',
45
+ data: {
46
+ message: rule.message ||
47
+ `Import "${importPath}" is forbidden in files matching "${rule.from}"`,
48
+ },
49
+ });
50
+ return; // Report once per import
51
+ }
52
+ }
53
+ }
54
+ },
55
+ };
56
+ },
57
+ });
58
+ //# sourceMappingURL=no-forbidden-imports.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"no-forbidden-imports.js","sourceRoot":"","sources":["../../src/rules/no-forbidden-imports.ts"],"names":[],"mappings":"AAAA,oCAAoC;AACpC,OAAO,EAAE,WAAW,EAAiB,MAAM,0BAA0B,CAAC;AACtE,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACnD,OAAO,EAAE,cAAc,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEvF,MAAM,UAAU,GAAG,WAAW,CAAC,WAAW,CACxC,CAAC,IAAI,EAAE,EAAE,CAAC,6EAA6E,IAAI,KAAK,CACjG,CAAC;AAIF,eAAe,UAAU,CAAiB;IACxC,IAAI,EAAE,sBAAsB;IAC5B,IAAI,EAAE;QACJ,IAAI,EAAE,SAAS;QACf,IAAI,EAAE;YACJ,WAAW,EAAE,wDAAwD;SACtE;QACD,QAAQ,EAAE;YACR,eAAe,EAAE,aAAa;SAC/B;QACD,MAAM,EAAE,EAAE;KACX;IACD,cAAc,EAAE,EAAE;IAClB,MAAM,CAAC,OAAO;QACZ,MAAM,MAAM,GAAG,SAAS,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;QAC3C,IAAI,CAAC,MAAM,EAAE,gBAAgB,EAAE,MAAM,EAAE,CAAC;YACtC,OAAO,EAAE,CAAC,CAAC,qBAAqB;QAClC,CAAC;QAED,oCAAoC;QACpC,MAAM,QAAQ,GAAG,aAAa,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;QAEjD,oCAAoC;QACpC,MAAM,eAAe,GAAG,MAAM,CAAC,gBAAgB,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAC9D,cAAc,CAAC,QAAQ,EAAE,IAAI,CAAC,IAAI,CAAC,CACpC,CAAC;QAEF,IAAI,eAAe,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACjC,OAAO,EAAE,CAAC,CAAC,8BAA8B;QAC3C,CAAC;QAED,OAAO;YACL,iBAAiB,CAAC,IAAgC;gBAChD,MAAM,UAAU,GAAG,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC;gBACrC,MAAM,cAAc,GAAG,iBAAiB,CAAC,UAAU,EAAE,OAAO,CAAC,QAAQ,CAAC,CAAC;gBAEvE,KAAK,MAAM,IAAI,IAAI,eAAe,EAAE,CAAC;oBACnC,KAAK,MAAM,UAAU,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;wBACvC,2CAA2C;wBAC3C,MAAM,OAAO,GACX,UAAU,KAAK,UAAU;4BACzB,cAAc,CAAC,cAAc,EAAE,UAAU,CAAC;4BAC1C,cAAc,CAAC,UAAU,EAAE,UAAU,CAAC,CAAC;wBAEzC,IAAI,OAAO,EAAE,CAAC;4BACZ,OAAO,CAAC,MAAM,CAAC;gCACb,IAAI;gCACJ,SAAS,EAAE,iBAAiB;gCAC5B,IAAI,EAAE;oCACJ,OAAO,EACL,IAAI,CAAC,OAAO;wCACZ,WAAW,UAAU,qCAAqC,IAAI,CAAC,IAAI,GAAG;iCACzE;6BACF,CAAC,CAAC;4BACH,OAAO,CAAC,yBAAyB;wBACnC,CAAC;oBACH,CAAC;gBACH,CAAC;YACH,CAAC;SACF,CAAC;IACJ,CAAC;CACF,CAAC,CAAC"}
@@ -0,0 +1,6 @@
1
+ import { ESLintUtils } from '@typescript-eslint/utils';
2
+ declare const _default: ESLintUtils.RuleModule<"layerViolation", [], unknown, ESLintUtils.RuleListener> & {
3
+ name: string;
4
+ };
5
+ export default _default;
6
+ //# sourceMappingURL=no-layer-violation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"no-layer-violation.d.ts","sourceRoot":"","sources":["../../src/rules/no-layer-violation.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAiB,MAAM,0BAA0B,CAAC;;;;AAetE,wBAiEG"}
@@ -0,0 +1,62 @@
1
+ // src/rules/no-layer-violation.ts
2
+ import { ESLintUtils } from '@typescript-eslint/utils';
3
+ import { getConfig } from '../utils/config-loader';
4
+ import { resolveImportPath, getLayerForFile, getLayerByName, normalizePath, } from '../utils/path-utils';
5
+ const createRule = ESLintUtils.RuleCreator((name) => `https://github.com/harness-engineering/eslint-plugin/blob/main/docs/rules/${name}.md`);
6
+ export default createRule({
7
+ name: 'no-layer-violation',
8
+ meta: {
9
+ type: 'problem',
10
+ docs: {
11
+ description: 'Enforce layer boundary imports',
12
+ },
13
+ messages: {
14
+ layerViolation: 'Layer "{{fromLayer}}" cannot import from layer "{{toLayer}}"',
15
+ },
16
+ schema: [],
17
+ },
18
+ defaultOptions: [],
19
+ create(context) {
20
+ const config = getConfig(context.filename);
21
+ if (!config?.layers?.length) {
22
+ return {}; // No-op if no layers configured
23
+ }
24
+ const filePath = normalizePath(context.filename);
25
+ const currentLayer = getLayerForFile(filePath, config.layers);
26
+ if (!currentLayer) {
27
+ return {}; // File not in any layer
28
+ }
29
+ const currentLayerDef = getLayerByName(currentLayer, config.layers);
30
+ if (!currentLayerDef) {
31
+ return {};
32
+ }
33
+ return {
34
+ ImportDeclaration(node) {
35
+ const importPath = node.source.value;
36
+ // Skip external imports
37
+ if (!importPath.startsWith('.')) {
38
+ return;
39
+ }
40
+ const resolvedImport = resolveImportPath(importPath, context.filename);
41
+ const importLayer = getLayerForFile(resolvedImport, config.layers);
42
+ // Skip if import is not in any layer
43
+ if (!importLayer) {
44
+ return;
45
+ }
46
+ // Check if import is allowed
47
+ if (importLayer !== currentLayer &&
48
+ !currentLayerDef.allowedDependencies.includes(importLayer)) {
49
+ context.report({
50
+ node,
51
+ messageId: 'layerViolation',
52
+ data: {
53
+ fromLayer: currentLayer,
54
+ toLayer: importLayer,
55
+ },
56
+ });
57
+ }
58
+ },
59
+ };
60
+ },
61
+ });
62
+ //# sourceMappingURL=no-layer-violation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"no-layer-violation.js","sourceRoot":"","sources":["../../src/rules/no-layer-violation.ts"],"names":[],"mappings":"AAAA,kCAAkC;AAClC,OAAO,EAAE,WAAW,EAAiB,MAAM,0BAA0B,CAAC;AACtE,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACnD,OAAO,EACL,iBAAiB,EACjB,eAAe,EACf,cAAc,EACd,aAAa,GACd,MAAM,qBAAqB,CAAC;AAE7B,MAAM,UAAU,GAAG,WAAW,CAAC,WAAW,CACxC,CAAC,IAAI,EAAE,EAAE,CAAC,6EAA6E,IAAI,KAAK,CACjG,CAAC;AAIF,eAAe,UAAU,CAAiB;IACxC,IAAI,EAAE,oBAAoB;IAC1B,IAAI,EAAE;QACJ,IAAI,EAAE,SAAS;QACf,IAAI,EAAE;YACJ,WAAW,EAAE,gCAAgC;SAC9C;QACD,QAAQ,EAAE;YACR,cAAc,EAAE,8DAA8D;SAC/E;QACD,MAAM,EAAE,EAAE;KACX;IACD,cAAc,EAAE,EAAE;IAClB,MAAM,CAAC,OAAO;QACZ,MAAM,MAAM,GAAG,SAAS,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;QAC3C,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;YAC5B,OAAO,EAAE,CAAC,CAAC,gCAAgC;QAC7C,CAAC;QAED,MAAM,QAAQ,GAAG,aAAa,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;QACjD,MAAM,YAAY,GAAG,eAAe,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC;QAE9D,IAAI,CAAC,YAAY,EAAE,CAAC;YAClB,OAAO,EAAE,CAAC,CAAC,wBAAwB;QACrC,CAAC;QAED,MAAM,eAAe,GAAG,cAAc,CAAC,YAAY,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC;QACpE,IAAI,CAAC,eAAe,EAAE,CAAC;YACrB,OAAO,EAAE,CAAC;QACZ,CAAC;QAED,OAAO;YACL,iBAAiB,CAAC,IAAgC;gBAChD,MAAM,UAAU,GAAG,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC;gBAErC,wBAAwB;gBACxB,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;oBAChC,OAAO;gBACT,CAAC;gBAED,MAAM,cAAc,GAAG,iBAAiB,CAAC,UAAU,EAAE,OAAO,CAAC,QAAQ,CAAC,CAAC;gBACvE,MAAM,WAAW,GAAG,eAAe,CAAC,cAAc,EAAE,MAAM,CAAC,MAAO,CAAC,CAAC;gBAEpE,qCAAqC;gBACrC,IAAI,CAAC,WAAW,EAAE,CAAC;oBACjB,OAAO;gBACT,CAAC;gBAED,6BAA6B;gBAC7B,IACE,WAAW,KAAK,YAAY;oBAC5B,CAAC,eAAe,CAAC,mBAAmB,CAAC,QAAQ,CAAC,WAAW,CAAC,EAC1D,CAAC;oBACD,OAAO,CAAC,MAAM,CAAC;wBACb,IAAI;wBACJ,SAAS,EAAE,gBAAgB;wBAC3B,IAAI,EAAE;4BACJ,SAAS,EAAE,YAAY;4BACvB,OAAO,EAAE,WAAW;yBACrB;qBACF,CAAC,CAAC;gBACL,CAAC;YACH,CAAC;SACF,CAAC;IACJ,CAAC;CACF,CAAC,CAAC"}
@@ -0,0 +1,6 @@
1
+ import { ESLintUtils } from '@typescript-eslint/utils';
2
+ declare const _default: ESLintUtils.RuleModule<"missingSchema", [], unknown, ESLintUtils.RuleListener> & {
3
+ name: string;
4
+ };
5
+ export default _default;
6
+ //# sourceMappingURL=require-boundary-schema.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"require-boundary-schema.d.ts","sourceRoot":"","sources":["../../src/rules/require-boundary-schema.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAiC,MAAM,0BAA0B,CAAC;;;;AAWtF,wBAsDG"}
@@ -0,0 +1,53 @@
1
+ // src/rules/require-boundary-schema.ts
2
+ import { ESLintUtils, AST_NODE_TYPES } from '@typescript-eslint/utils';
3
+ import { getConfig } from '../utils/config-loader';
4
+ import { matchesPattern, normalizePath } from '../utils/path-utils';
5
+ import { hasZodValidation } from '../utils/ast-helpers';
6
+ const createRule = ESLintUtils.RuleCreator((name) => `https://github.com/harness-engineering/eslint-plugin/blob/main/docs/rules/${name}.md`);
7
+ export default createRule({
8
+ name: 'require-boundary-schema',
9
+ meta: {
10
+ type: 'problem',
11
+ docs: {
12
+ description: 'Require Zod schema validation at API boundaries',
13
+ },
14
+ messages: {
15
+ missingSchema: 'Exported function "{{name}}" at API boundary must validate input with Zod schema',
16
+ },
17
+ schema: [],
18
+ },
19
+ defaultOptions: [],
20
+ create(context) {
21
+ const config = getConfig(context.filename);
22
+ if (!config?.boundaries?.requireSchema?.length) {
23
+ return {}; // No-op if no boundaries configured
24
+ }
25
+ const filePath = normalizePath(context.filename);
26
+ // Check if file matches any boundary pattern
27
+ const isBoundaryFile = config.boundaries.requireSchema.some((pattern) => matchesPattern(filePath, pattern));
28
+ if (!isBoundaryFile) {
29
+ return {}; // Not a boundary file
30
+ }
31
+ return {
32
+ ExportNamedDeclaration(node) {
33
+ const decl = node.declaration;
34
+ // Only check function declarations
35
+ if (!decl || decl.type !== AST_NODE_TYPES.FunctionDeclaration) {
36
+ return;
37
+ }
38
+ const fn = decl;
39
+ if (!fn.id || !fn.body)
40
+ return;
41
+ // Check if function has Zod validation
42
+ if (!hasZodValidation(fn.body)) {
43
+ context.report({
44
+ node: fn,
45
+ messageId: 'missingSchema',
46
+ data: { name: fn.id.name },
47
+ });
48
+ }
49
+ },
50
+ };
51
+ },
52
+ });
53
+ //# sourceMappingURL=require-boundary-schema.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"require-boundary-schema.js","sourceRoot":"","sources":["../../src/rules/require-boundary-schema.ts"],"names":[],"mappings":"AAAA,uCAAuC;AACvC,OAAO,EAAE,WAAW,EAAE,cAAc,EAAiB,MAAM,0BAA0B,CAAC;AACtF,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACnD,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpE,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAExD,MAAM,UAAU,GAAG,WAAW,CAAC,WAAW,CACxC,CAAC,IAAI,EAAE,EAAE,CAAC,6EAA6E,IAAI,KAAK,CACjG,CAAC;AAIF,eAAe,UAAU,CAAiB;IACxC,IAAI,EAAE,yBAAyB;IAC/B,IAAI,EAAE;QACJ,IAAI,EAAE,SAAS;QACf,IAAI,EAAE;YACJ,WAAW,EAAE,iDAAiD;SAC/D;QACD,QAAQ,EAAE;YACR,aAAa,EACX,kFAAkF;SACrF;QACD,MAAM,EAAE,EAAE;KACX;IACD,cAAc,EAAE,EAAE;IAClB,MAAM,CAAC,OAAO;QACZ,MAAM,MAAM,GAAG,SAAS,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;QAC3C,IAAI,CAAC,MAAM,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,EAAE,CAAC;YAC/C,OAAO,EAAE,CAAC,CAAC,oCAAoC;QACjD,CAAC;QAED,MAAM,QAAQ,GAAG,aAAa,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;QAEjD,6CAA6C;QAC7C,MAAM,cAAc,GAAG,MAAM,CAAC,UAAU,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,CACtE,cAAc,CAAC,QAAQ,EAAE,OAAO,CAAC,CAClC,CAAC;QAEF,IAAI,CAAC,cAAc,EAAE,CAAC;YACpB,OAAO,EAAE,CAAC,CAAC,sBAAsB;QACnC,CAAC;QAED,OAAO;YACL,sBAAsB,CAAC,IAAqC;gBAC1D,MAAM,IAAI,GAAG,IAAI,CAAC,WAAW,CAAC;gBAE9B,mCAAmC;gBACnC,IAAI,CAAC,IAAI,IAAK,IAAI,CAAC,IAAuB,KAAK,cAAc,CAAC,mBAAmB,EAAE,CAAC;oBAClF,OAAO;gBACT,CAAC;gBAED,MAAM,EAAE,GAAG,IAAoC,CAAC;gBAChD,IAAI,CAAC,EAAE,CAAC,EAAE,IAAI,CAAC,EAAE,CAAC,IAAI;oBAAE,OAAO;gBAE/B,uCAAuC;gBACvC,IAAI,CAAC,gBAAgB,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;oBAC/B,OAAO,CAAC,MAAM,CAAC;wBACb,IAAI,EAAE,EAAE;wBACR,SAAS,EAAE,eAAe;wBAC1B,IAAI,EAAE,EAAE,IAAI,EAAE,EAAE,CAAC,EAAE,CAAC,IAAI,EAAE;qBAC3B,CAAC,CAAC;gBACL,CAAC;YACH,CAAC;SACF,CAAC;IACJ,CAAC;CACF,CAAC,CAAC"}
@@ -0,0 +1,14 @@
1
+ import { type TSESTree } from '@typescript-eslint/utils';
2
+ /**
3
+ * Check if a node has a preceding JSDoc comment
4
+ */
5
+ export declare function hasJSDocComment(node: TSESTree.Node, sourceCode: string): boolean;
6
+ /**
7
+ * Check if a function body contains Zod validation (.parse or .safeParse)
8
+ */
9
+ export declare function hasZodValidation(body: TSESTree.BlockStatement): boolean;
10
+ /**
11
+ * Check if a node is marked with @internal
12
+ */
13
+ export declare function isMarkedInternal(node: TSESTree.Node, sourceCode: string): boolean;
14
+ //# sourceMappingURL=ast-helpers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ast-helpers.d.ts","sourceRoot":"","sources":["../../src/utils/ast-helpers.ts"],"names":[],"mappings":"AACA,OAAO,EAAkB,KAAK,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AAEzE;;GAEG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,QAAQ,CAAC,IAAI,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CA+BhF;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,QAAQ,CAAC,cAAc,GAAG,OAAO,CA+CvE;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,QAAQ,CAAC,IAAI,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAYjF"}
@@ -0,0 +1,94 @@
1
+ // src/utils/ast-helpers.ts
2
+ import { AST_NODE_TYPES } from '@typescript-eslint/utils';
3
+ /**
4
+ * Check if a node has a preceding JSDoc comment
5
+ */
6
+ export function hasJSDocComment(node, sourceCode) {
7
+ if (!node.range)
8
+ return false;
9
+ // Get text before the node
10
+ const textBefore = sourceCode.slice(0, node.range[0]);
11
+ const lines = textBefore.split('\n');
12
+ // Look backwards for /** ... */
13
+ let foundJSDoc = false;
14
+ for (let i = lines.length - 1; i >= 0; i--) {
15
+ const line = lines[i]?.trim() ?? '';
16
+ // Empty line or whitespace - keep looking
17
+ if (line === '')
18
+ continue;
19
+ // Found JSDoc end
20
+ if (line.endsWith('*/')) {
21
+ // Check if it's JSDoc (starts with /**)
22
+ const startIdx = textBefore.lastIndexOf('/**');
23
+ const endIdx = textBefore.lastIndexOf('*/');
24
+ if (startIdx !== -1 && endIdx > startIdx) {
25
+ foundJSDoc = true;
26
+ }
27
+ break;
28
+ }
29
+ // Found something else - no JSDoc
30
+ break;
31
+ }
32
+ return foundJSDoc;
33
+ }
34
+ /**
35
+ * Check if a function body contains Zod validation (.parse or .safeParse)
36
+ */
37
+ export function hasZodValidation(body) {
38
+ let found = false;
39
+ // Keys to skip to avoid circular references
40
+ const skipKeys = new Set(['parent', 'loc', 'range', 'tokens', 'comments']);
41
+ function visit(node) {
42
+ if (found)
43
+ return;
44
+ if (node.type === AST_NODE_TYPES.CallExpression &&
45
+ node.callee.type ===
46
+ AST_NODE_TYPES.MemberExpression) {
47
+ const prop = node.callee.property;
48
+ const propType = prop.type;
49
+ if (propType === AST_NODE_TYPES.Identifier &&
50
+ (prop.name === 'parse' ||
51
+ prop.name === 'safeParse')) {
52
+ found = true;
53
+ return;
54
+ }
55
+ }
56
+ // Recursively visit children
57
+ for (const key of Object.keys(node)) {
58
+ if (skipKeys.has(key))
59
+ continue;
60
+ const value = node[key];
61
+ if (value && typeof value === 'object') {
62
+ if (Array.isArray(value)) {
63
+ for (const item of value) {
64
+ if (item && typeof item === 'object' && 'type' in item) {
65
+ visit(item);
66
+ }
67
+ }
68
+ }
69
+ else if ('type' in value) {
70
+ visit(value);
71
+ }
72
+ }
73
+ }
74
+ }
75
+ visit(body);
76
+ return found;
77
+ }
78
+ /**
79
+ * Check if a node is marked with @internal
80
+ */
81
+ export function isMarkedInternal(node, sourceCode) {
82
+ if (!node.range)
83
+ return false;
84
+ const textBefore = sourceCode.slice(0, node.range[0]);
85
+ const lastComment = textBefore.lastIndexOf('/**');
86
+ if (lastComment === -1)
87
+ return false;
88
+ const commentEnd = textBefore.lastIndexOf('*/');
89
+ if (commentEnd === -1 || commentEnd < lastComment)
90
+ return false;
91
+ const comment = textBefore.slice(lastComment, commentEnd + 2);
92
+ return comment.includes('@internal');
93
+ }
94
+ //# sourceMappingURL=ast-helpers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ast-helpers.js","sourceRoot":"","sources":["../../src/utils/ast-helpers.ts"],"names":[],"mappings":"AAAA,2BAA2B;AAC3B,OAAO,EAAE,cAAc,EAAiB,MAAM,0BAA0B,CAAC;AAEzE;;GAEG;AACH,MAAM,UAAU,eAAe,CAAC,IAAmB,EAAE,UAAkB;IACrE,IAAI,CAAC,IAAI,CAAC,KAAK;QAAE,OAAO,KAAK,CAAC;IAE9B,2BAA2B;IAC3B,MAAM,UAAU,GAAG,UAAU,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;IACtD,MAAM,KAAK,GAAG,UAAU,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAErC,gCAAgC;IAChC,IAAI,UAAU,GAAG,KAAK,CAAC;IACvB,KAAK,IAAI,CAAC,GAAG,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;QAC3C,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;QAEpC,0CAA0C;QAC1C,IAAI,IAAI,KAAK,EAAE;YAAE,SAAS;QAE1B,kBAAkB;QAClB,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;YACxB,wCAAwC;YACxC,MAAM,QAAQ,GAAG,UAAU,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;YAC/C,MAAM,MAAM,GAAG,UAAU,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC;YAC5C,IAAI,QAAQ,KAAK,CAAC,CAAC,IAAI,MAAM,GAAG,QAAQ,EAAE,CAAC;gBACzC,UAAU,GAAG,IAAI,CAAC;YACpB,CAAC;YACD,MAAM;QACR,CAAC;QAED,kCAAkC;QAClC,MAAM;IACR,CAAC;IAED,OAAO,UAAU,CAAC;AACpB,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,gBAAgB,CAAC,IAA6B;IAC5D,IAAI,KAAK,GAAG,KAAK,CAAC;IAElB,4CAA4C;IAC5C,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,CAAC,QAAQ,EAAE,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,UAAU,CAAC,CAAC,CAAC;IAE3E,SAAS,KAAK,CAAC,IAAmB;QAChC,IAAI,KAAK;YAAE,OAAO;QAElB,IACG,IAAI,CAAC,IAAuB,KAAK,cAAc,CAAC,cAAc;YAC7D,IAAgC,CAAC,MAAM,CAAC,IAAuB;gBAC/D,cAAc,CAAC,gBAAgB,EACjC,CAAC;YACD,MAAM,IAAI,GAAK,IAAgC,CAAC,MAAoC,CAAC,QAAQ,CAAC;YAC9F,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAsB,CAAC;YAC7C,IACE,QAAQ,KAAK,cAAc,CAAC,UAAU;gBACtC,CAAE,IAA4B,CAAC,IAAI,KAAK,OAAO;oBAC5C,IAA4B,CAAC,IAAI,KAAK,WAAW,CAAC,EACrD,CAAC;gBACD,KAAK,GAAG,IAAI,CAAC;gBACb,OAAO;YACT,CAAC;QACH,CAAC;QAED,6BAA6B;QAC7B,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;YACpC,IAAI,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC;gBAAE,SAAS;YAEhC,MAAM,KAAK,GAAI,IAA2C,CAAC,GAAG,CAAC,CAAC;YAChE,IAAI,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;gBACvC,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;oBACzB,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;wBACzB,IAAI,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,MAAM,IAAI,IAAI,EAAE,CAAC;4BACvD,KAAK,CAAC,IAAqB,CAAC,CAAC;wBAC/B,CAAC;oBACH,CAAC;gBACH,CAAC;qBAAM,IAAI,MAAM,IAAI,KAAK,EAAE,CAAC;oBAC3B,KAAK,CAAC,KAAsB,CAAC,CAAC;gBAChC,CAAC;YACH,CAAC;QACH,CAAC;IACH,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,CAAC;IACZ,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,gBAAgB,CAAC,IAAmB,EAAE,UAAkB;IACtE,IAAI,CAAC,IAAI,CAAC,KAAK;QAAE,OAAO,KAAK,CAAC;IAE9B,MAAM,UAAU,GAAG,UAAU,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;IACtD,MAAM,WAAW,GAAG,UAAU,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;IAClD,IAAI,WAAW,KAAK,CAAC,CAAC;QAAE,OAAO,KAAK,CAAC;IAErC,MAAM,UAAU,GAAG,UAAU,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC;IAChD,IAAI,UAAU,KAAK,CAAC,CAAC,IAAI,UAAU,GAAG,WAAW;QAAE,OAAO,KAAK,CAAC;IAEhE,MAAM,OAAO,GAAG,UAAU,CAAC,KAAK,CAAC,WAAW,EAAE,UAAU,GAAG,CAAC,CAAC,CAAC;IAC9D,OAAO,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;AACvC,CAAC"}
@@ -0,0 +1,10 @@
1
+ import { type HarnessConfig } from './schema';
2
+ /**
3
+ * Load and validate config, with caching
4
+ */
5
+ export declare function getConfig(filePath: string): HarnessConfig | null;
6
+ /**
7
+ * Clear the config cache (for testing)
8
+ */
9
+ export declare function clearConfigCache(): void;
10
+ //# sourceMappingURL=config-loader.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config-loader.d.ts","sourceRoot":"","sources":["../../src/utils/config-loader.ts"],"names":[],"mappings":"AAGA,OAAO,EAAuB,KAAK,aAAa,EAAE,MAAM,UAAU,CAAC;AAwBnE;;GAEG;AACH,wBAAgB,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,aAAa,GAAG,IAAI,CAuBhE;AAED;;GAEG;AACH,wBAAgB,gBAAgB,IAAI,IAAI,CAGvC"}
@@ -0,0 +1,56 @@
1
+ // src/utils/config-loader.ts
2
+ import * as fs from 'fs';
3
+ import * as path from 'path';
4
+ import { HarnessConfigSchema } from './schema';
5
+ const CONFIG_FILENAME = 'harness.config.json';
6
+ let cachedConfig = null;
7
+ let cachedConfigPath = null;
8
+ /**
9
+ * Find harness.config.json by walking up from the given directory
10
+ */
11
+ function findConfigFile(startDir) {
12
+ let currentDir = path.resolve(startDir);
13
+ const root = path.parse(currentDir).root;
14
+ while (currentDir !== root) {
15
+ const configPath = path.join(currentDir, CONFIG_FILENAME);
16
+ if (fs.existsSync(configPath)) {
17
+ return configPath;
18
+ }
19
+ currentDir = path.dirname(currentDir);
20
+ }
21
+ return null;
22
+ }
23
+ /**
24
+ * Load and validate config, with caching
25
+ */
26
+ export function getConfig(filePath) {
27
+ const configPath = findConfigFile(path.dirname(filePath));
28
+ if (!configPath) {
29
+ return null;
30
+ }
31
+ // Return cached config if same path
32
+ if (cachedConfigPath === configPath && cachedConfig) {
33
+ return cachedConfig;
34
+ }
35
+ try {
36
+ const content = fs.readFileSync(configPath, 'utf-8');
37
+ const parsed = HarnessConfigSchema.safeParse(JSON.parse(content));
38
+ if (!parsed.success) {
39
+ return null;
40
+ }
41
+ cachedConfig = parsed.data;
42
+ cachedConfigPath = configPath;
43
+ return cachedConfig;
44
+ }
45
+ catch {
46
+ return null;
47
+ }
48
+ }
49
+ /**
50
+ * Clear the config cache (for testing)
51
+ */
52
+ export function clearConfigCache() {
53
+ cachedConfig = null;
54
+ cachedConfigPath = null;
55
+ }
56
+ //# sourceMappingURL=config-loader.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config-loader.js","sourceRoot":"","sources":["../../src/utils/config-loader.ts"],"names":[],"mappings":"AAAA,6BAA6B;AAC7B,OAAO,KAAK,EAAE,MAAM,IAAI,CAAC;AACzB,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAC7B,OAAO,EAAE,mBAAmB,EAAsB,MAAM,UAAU,CAAC;AAEnE,MAAM,eAAe,GAAG,qBAAqB,CAAC;AAE9C,IAAI,YAAY,GAAyB,IAAI,CAAC;AAC9C,IAAI,gBAAgB,GAAkB,IAAI,CAAC;AAE3C;;GAEG;AACH,SAAS,cAAc,CAAC,QAAgB;IACtC,IAAI,UAAU,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IACxC,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC;IAEzC,OAAO,UAAU,KAAK,IAAI,EAAE,CAAC;QAC3B,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,eAAe,CAAC,CAAC;QAC1D,IAAI,EAAE,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;YAC9B,OAAO,UAAU,CAAC;QACpB,CAAC;QACD,UAAU,GAAG,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;IACxC,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,SAAS,CAAC,QAAgB;IACxC,MAAM,UAAU,GAAG,cAAc,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC;IAC1D,IAAI,CAAC,UAAU,EAAE,CAAC;QAChB,OAAO,IAAI,CAAC;IACd,CAAC;IAED,oCAAoC;IACpC,IAAI,gBAAgB,KAAK,UAAU,IAAI,YAAY,EAAE,CAAC;QACpD,OAAO,YAAY,CAAC;IACtB,CAAC;IAED,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,EAAE,CAAC,YAAY,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;QACrD,MAAM,MAAM,GAAG,mBAAmB,CAAC,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC;QAClE,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;YACpB,OAAO,IAAI,CAAC;QACd,CAAC;QACD,YAAY,GAAG,MAAM,CAAC,IAAI,CAAC;QAC3B,gBAAgB,GAAG,UAAU,CAAC;QAC9B,OAAO,YAAY,CAAC;IACtB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,gBAAgB;IAC9B,YAAY,GAAG,IAAI,CAAC;IACpB,gBAAgB,GAAG,IAAI,CAAC;AAC1B,CAAC"}
@@ -0,0 +1,5 @@
1
+ export * from './schema';
2
+ export * from './config-loader';
3
+ export * from './path-utils';
4
+ export * from './ast-helpers';
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AACA,cAAc,UAAU,CAAC;AACzB,cAAc,iBAAiB,CAAC;AAChC,cAAc,cAAc,CAAC;AAC7B,cAAc,eAAe,CAAC"}
@@ -0,0 +1,6 @@
1
+ // src/utils/index.ts
2
+ export * from './schema';
3
+ export * from './config-loader';
4
+ export * from './path-utils';
5
+ export * from './ast-helpers';
6
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA,qBAAqB;AACrB,cAAc,UAAU,CAAC;AACzB,cAAc,iBAAiB,CAAC;AAChC,cAAc,cAAc,CAAC;AAC7B,cAAc,eAAe,CAAC"}
@@ -0,0 +1,24 @@
1
+ import type { Layer } from './schema';
2
+ /**
3
+ * Resolve an import path relative to the importing file
4
+ * Returns path relative to project root (assumes /project/ prefix)
5
+ */
6
+ export declare function resolveImportPath(importPath: string, importingFile: string): string;
7
+ /**
8
+ * Check if a file path matches a glob pattern
9
+ */
10
+ export declare function matchesPattern(filePath: string, pattern: string): boolean;
11
+ /**
12
+ * Find which layer a file belongs to
13
+ */
14
+ export declare function getLayerForFile(filePath: string, layers: Layer[]): string | null;
15
+ /**
16
+ * Get layer definition by name
17
+ */
18
+ export declare function getLayerByName(name: string, layers: Layer[]): Layer | undefined;
19
+ /**
20
+ * Normalize a file path to project-relative format
21
+ * Extracts path from /any/path/src/... to src/...
22
+ */
23
+ export declare function normalizePath(filePath: string): string;
24
+ //# sourceMappingURL=path-utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"path-utils.d.ts","sourceRoot":"","sources":["../../src/utils/path-utils.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,UAAU,CAAC;AAEtC;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,UAAU,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,GAAG,MAAM,CAmBnF;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAMzE;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,MAAM,GAAG,IAAI,CAOhF;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,KAAK,GAAG,SAAS,CAE/E;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAMtD"}