@leancodepl/folder-structure-cruiser 10.3.1 → 10.5.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/CHANGELOG.md CHANGED
@@ -3,6 +3,32 @@
3
3
  All notable changes to this project will be documented in this file. See
4
4
  [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ # [10.5.0](https://github.com/leancodepl/js_corelibrary/compare/v10.4.0...v10.5.0) (2026-07-01)
7
+
8
+ ### Bug Fixes
9
+
10
+ - do not return exit code 1 for validateSharedComponent
11
+ ([ec5240e](https://github.com/leancodepl/js_corelibrary/commit/ec5240ee4733dbe32550e188c78b451894c873ee))
12
+ - strip opaque segments in checkSharedComponents
13
+ ([73b9fc7](https://github.com/leancodepl/js_corelibrary/commit/73b9fc71e875f9e7bbb9c58ee88387e424f7f53a))
14
+
15
+ # Change Log
16
+
17
+ All notable changes to this project will be documented in this file. See
18
+ [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
19
+
20
+ # [10.4.0](https://github.com/leancodepl/js_corelibrary/compare/v10.3.1...v10.4.0) (2026-06-23)
21
+
22
+ ### Features
23
+
24
+ - skip opaque segments and external dependencies in cross-feature-imports check
25
+ ([b2cb6dd](https://github.com/leancodepl/js_corelibrary/commit/b2cb6ddf79345444c5dd20bdaadcb8901c99e510))
26
+
27
+ # Change Log
28
+
29
+ All notable changes to this project will be documented in this file. See
30
+ [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
31
+
6
32
  ## [10.3.1](https://github.com/leancodepl/js_corelibrary/compare/v10.3.0...v10.3.1) (2026-05-20)
7
33
 
8
34
  ### Bug Fixes
package/README.md CHANGED
@@ -97,6 +97,10 @@ Imports are allowed only if they meet one of these conditions:
97
97
  2. **Sibling's immediate child**: Import is from an immediate sibling's or ancestors siblings' child
98
98
  - ✅ `src/feature1/subfeature1/ComponentA` → `src/feature1/subfeature2/ComponentB`
99
99
 
100
+ 3. **Through an opaque directory**: any directory whose name ends with an underscore is [opaque](#opaque-directories)
101
+ and doesn't count toward the nesting depth
102
+ - ✅ `src/feature1/ComponentA` → `src/features_/feature2/ComponentB`
103
+
100
104
  ### Shared Component Detection
101
105
 
102
106
  Identifies components that should be moved to shared levels when:
@@ -116,6 +120,23 @@ Create a `.dependency-cruiser.json` file in your project root:
116
120
 
117
121
  This configuration serves as a base config that can be extended with your own rules.
118
122
 
123
+ ### Opaque directories
124
+
125
+ Some directories only group features together (e.g. a `features/` folder that holds every feature) rather than being a
126
+ feature themselves. Counting them toward the nesting depth would make every cross-feature import look one level too
127
+ deep. Mark such a directory as **opaque** by ending its name with an underscore — `features_`, `modules_`, etc. Opaque
128
+ segments are skipped when measuring how deep an import reaches, so the directory behaves as if it weren't there.
129
+
130
+ This needs no configuration; it's driven purely by the directory name. With a `src/features_/` container:
131
+
132
+ - ✅ `src/features_/featureA/X` → `src/features_/featureB/Y` (`features_` is skipped, so these read as sibling features)
133
+ - ✅ `src/featureA/X` → `src/features_/featureB/Y` (`featureB` is a top-level feature once `features_` is skipped)
134
+ - ❌ `src/featureA/X` → `src/features_/featureB/internal/Y` (`internal/Y` is still a grandchild of `featureB`, one level
135
+ too deep)
136
+
137
+ The trailing-underscore convention is matched verbatim, so a sibling directory like `features` (no underscore) keeps
138
+ counting normally.
139
+
119
140
  ## Nx Configuration
120
141
 
121
142
  Configure folder-structure-cruiser commands as Nx target in your `project.json`. Example configuration:
package/dist/bin.cjs CHANGED
@@ -1,2 +1,2 @@
1
1
  #!/usr/bin/env node
2
- "use strict";const t=require("commander"),r=require("./validateSharedComponent-oM_e4L8S.cjs");t.program.name("folder-structure-cruiser").description("CLI tool for validating folder structure rules");function s(o){o>0&&(process.exitCode=1)}function f(o){r.logger.error(o instanceof Error?o:new Error(String(o))),process.exitCode=1}t.program.command("validate-shared-components").description("Validate if shared components are located at the first shared level").option("-d, --directory <dir>","Directory to analyze",".").option("-c, --config <path_to_config>","Path to config file").option("-t, --tsConfig <path_to_ts_config>","Path to ts config file").option("-w, --webpackConfig <path_to_webpack_config>","Path to webpack config file").action(async o=>{const e=o.directory?[o.directory]:[".*"],i=o.config??"",a=o.tsConfig,n=o.webpackConfig,c=await r.validateSharedComponent({directories:e,configPath:i,tsConfigPath:a,webpackConfigPath:n});s(c)});t.program.command("validate-cross-feature-imports").description("Validate if cross-feature nested imports are allowed").option("-d, --directory <dir>","Directory to analyze",".").option("-c, --config <path_to_config>","Path to config file").option("-t, --tsConfig <path_to_ts_config>","Path to ts config file").option("-w, --webpackConfig <path_to_webpack_config>","Path to webpack config file").action(async o=>{const e=o.directory?[o.directory]:[".*"],i=o.config??"",a=o.tsConfig,n=o.webpackConfig,c=await r.validateCrossFeatureImports({directories:e,configPath:i,tsConfigPath:a,webpackConfigPath:n});s(c)});t.program.parseAsync().catch(f);
2
+ "use strict";const t=require("commander"),r=require("./validateSharedComponent-DaLncijO.cjs");t.program.name("folder-structure-cruiser").description("CLI tool for validating folder structure rules");function s(o){o>0&&(process.exitCode=1)}function f(o){r.logger.error(o instanceof Error?o:new Error(String(o))),process.exitCode=1}t.program.command("validate-shared-components").description("Validate if shared components are located at the first shared level").option("-d, --directory <dir>","Directory to analyze",".").option("-c, --config <path_to_config>","Path to config file").option("-t, --tsConfig <path_to_ts_config>","Path to ts config file").option("-w, --webpackConfig <path_to_webpack_config>","Path to webpack config file").action(async o=>{const e=o.directory?[o.directory]:[".*"],i=o.config??"",a=o.tsConfig,n=o.webpackConfig,c=await r.validateSharedComponent({directories:e,configPath:i,tsConfigPath:a,webpackConfigPath:n});s(c)});t.program.command("validate-cross-feature-imports").description("Validate if cross-feature nested imports are allowed").option("-d, --directory <dir>","Directory to analyze",".").option("-c, --config <path_to_config>","Path to config file").option("-t, --tsConfig <path_to_ts_config>","Path to ts config file").option("-w, --webpackConfig <path_to_webpack_config>","Path to webpack config file").action(async o=>{const e=o.directory?[o.directory]:[".*"],i=o.config??"",a=o.tsConfig,n=o.webpackConfig,c=await r.validateCrossFeatureImports({directories:e,configPath:i,tsConfigPath:a,webpackConfigPath:n});s(c)});t.program.parseAsync().catch(f);
package/dist/bin.js CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import { program as t } from "commander";
3
- import { a as s, v as f, l as d } from "./validateSharedComponent-CZqzEHqN.js";
3
+ import { a as s, v as f, l as d } from "./validateSharedComponent-_ujfixtH.js";
4
4
  t.name("folder-structure-cruiser").description("CLI tool for validating folder structure rules");
5
5
  function r(o) {
6
6
  o > 0 && (process.exitCode = 1);
@@ -2,10 +2,11 @@ import { CruiseParams } from '../lib/getCruiseResult.js';
2
2
  /**
3
3
  * Validates cross-feature nested imports according to folder structure rules.
4
4
  *
5
- * This function analyzes the codebase using dependency-cruiser to identify violations
6
- * of cross-feature import restrictions. It checks if modules with multiple dependents
7
- * are properly structured to avoid cross-feature nested imports that violate the
8
- * established folder structure rules.
5
+ * Analyzes the module graph of the given directories and reports imports that
6
+ * reach into another feature deeper than its immediate children. Directories
7
+ * whose name ends with an underscore (e.g. `features_`) are opaque and don't
8
+ * count toward the nesting depth. Imports of Node built-ins and npm packages
9
+ * are left out of the analysis.
9
10
  *
10
11
  * The function will output violations to the console, showing which modules have
11
12
  * cross-feature import issues that need to be resolved.
@@ -16,7 +17,7 @@ import { CruiseParams } from '../lib/getCruiseResult.js';
16
17
  * @param cruiseParams.tsConfigPath - Optional path to TypeScript configuration file for enhanced type resolution
17
18
  * @param cruiseParams.webpackConfigPath - Optional path to webpack configuration file for webpack alias resolution
18
19
  *
19
- * @returns Promise<number> - Number of detected violations
20
+ * @returns Promise<number> - Number of error-level violations
20
21
  *
21
22
  * @throws {Error} - Throws an error if the dependency analysis fails or configuration is invalid
22
23
  *
@@ -1 +1 @@
1
- {"version":3,"file":"validateCrossFeatureImports.d.ts","sourceRoot":"","sources":["../../src/commands/validateCrossFeatureImports.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,YAAY,EAAmB,MAAM,2BAA2B,CAAA;AAGzE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwDG;AACH,wBAAsB,2BAA2B,CAAC,YAAY,EAAE,YAAY,GAAG,OAAO,CAAC,MAAM,CAAC,CAgB7F"}
1
+ {"version":3,"file":"validateCrossFeatureImports.d.ts","sourceRoot":"","sources":["../../src/commands/validateCrossFeatureImports.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,YAAY,EAAmB,MAAM,2BAA2B,CAAA;AAGzE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyDG;AACH,wBAAsB,2BAA2B,CAAC,YAAY,EAAE,YAAY,GAAG,OAAO,CAAC,MAAM,CAAC,CAgB7F"}
@@ -16,7 +16,8 @@ import { CruiseParams } from '../lib/getCruiseResult.js';
16
16
  * @param cruiseParams.tsConfigPath - Optional path to TypeScript configuration file for enhanced type resolution
17
17
  * @param cruiseParams.webpackConfigPath - Optional path to webpack configuration file for webpack alias resolution
18
18
  *
19
- * @returns Promise<number> - Number of detected violations
19
+ * @returns Promise<number> - Number of error-level violations. This rule is purely informational,
20
+ * so it always resolves to 0 and never fails the command, even when recommendations are reported.
20
21
  *
21
22
  * @throws {Error} - Throws an error if the dependency analysis fails or configuration is invalid
22
23
  *
@@ -1 +1 @@
1
- {"version":3,"file":"validateSharedComponent.d.ts","sourceRoot":"","sources":["../../src/commands/validateSharedComponent.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,YAAY,EAAmB,MAAM,2BAA2B,CAAA;AAGzE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwDG;AACH,wBAAsB,uBAAuB,CAAC,YAAY,EAAE,YAAY,GAAG,OAAO,CAAC,MAAM,CAAC,CAgBzF"}
1
+ {"version":3,"file":"validateSharedComponent.d.ts","sourceRoot":"","sources":["../../src/commands/validateSharedComponent.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,YAAY,EAAmB,MAAM,2BAA2B,CAAA;AAGzE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyDG;AACH,wBAAsB,uBAAuB,CAAC,YAAY,EAAE,YAAY,GAAG,OAAO,CAAC,MAAM,CAAC,CAiBzF"}
package/dist/index.cjs CHANGED
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("./validateSharedComponent-oM_e4L8S.cjs");exports.validateCrossFeatureImports=e.validateCrossFeatureImports;exports.validateSharedComponent=e.validateSharedComponent;
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("./validateSharedComponent-DaLncijO.cjs");exports.validateCrossFeatureImports=e.validateCrossFeatureImports;exports.validateSharedComponent=e.validateSharedComponent;
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { v as o, a as r } from "./validateSharedComponent-CZqzEHqN.js";
1
+ import { v as o, a as r } from "./validateSharedComponent-_ujfixtH.js";
2
2
  export {
3
3
  o as validateCrossFeatureImports,
4
4
  r as validateSharedComponent
@@ -1 +1 @@
1
- {"version":3,"file":"checkCrossFeatureImports.d.ts","sourceRoot":"","sources":["../../src/lib/checkCrossFeatureImports.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAA;AAEpD,OAAO,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAE7C,KAAK,WAAW,GAAG;IAAE,QAAQ,EAAE,OAAO,EAAE,CAAC;IAAC,YAAY,EAAE,MAAM,CAAA;CAAE,CAAA;AAEhE,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,eAAe,GAAG,WAAW,CAqC7E"}
1
+ {"version":3,"file":"checkCrossFeatureImports.d.ts","sourceRoot":"","sources":["../../src/lib/checkCrossFeatureImports.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAA;AAEpD,OAAO,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAG7C,KAAK,WAAW,GAAG;IAAE,QAAQ,EAAE,OAAO,EAAE,CAAC;IAAC,YAAY,EAAE,MAAM,CAAA;CAAE,CAAA;AAEhE,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,eAAe,GAAG,WAAW,CA0C7E"}
@@ -1 +1 @@
1
- {"version":3,"file":"checkSharedComponents.d.ts","sourceRoot":"","sources":["../../src/lib/checkSharedComponents.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAA;AAEpD,OAAO,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAE7C,KAAK,WAAW,GAAG;IAAE,QAAQ,EAAE,OAAO,EAAE,CAAC;IAAC,YAAY,EAAE,MAAM,CAAA;CAAE,CAAA;AAmBhE,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,eAAe,GAAG,WAAW,CA8C1E"}
1
+ {"version":3,"file":"checkSharedComponents.d.ts","sourceRoot":"","sources":["../../src/lib/checkSharedComponents.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAA;AAEpD,OAAO,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAG7C,KAAK,WAAW,GAAG;IAAE,QAAQ,EAAE,OAAO,EAAE,CAAC;IAAC,YAAY,EAAE,MAAM,CAAA;CAAE,CAAA;AAmBhE,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,eAAe,GAAG,WAAW,CA8C1E"}
@@ -1 +1 @@
1
- {"version":3,"file":"findCommonPathsPrefix.d.ts","sourceRoot":"","sources":["../../src/lib/findCommonPathsPrefix.ts"],"names":[],"mappings":"AAAA,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,GAAG,MAAM,EAAE,CAiBjE;AAED,wBAAgB,2BAA2B,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,GAAG,MAAM,CAErE"}
1
+ {"version":3,"file":"findCommonPathsPrefix.d.ts","sourceRoot":"","sources":["../../src/lib/findCommonPathsPrefix.ts"],"names":[],"mappings":"AAAA,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,GAAG,MAAM,EAAE,CAmBjE;AAED,wBAAgB,2BAA2B,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,GAAG,MAAM,CAErE"}
@@ -1,9 +1,9 @@
1
1
  export declare const logger: import('packages/logger/dist/lib/logger').Logger<{}, {
2
- error: import('packages/logger/dist/lib/logger').MethodHandler<unknown>;
3
- warn: import('packages/logger/dist/lib/logger').MethodHandler<unknown>;
4
- success: import('packages/logger/dist/lib/logger').MethodHandler<unknown>;
5
- info: import('packages/logger/dist/lib/logger').MethodHandler<unknown>;
6
- verbose: import('packages/logger/dist/lib/logger').MethodHandler<unknown>;
7
- debug: import('packages/logger/dist/lib/logger').MethodHandler<unknown>;
2
+ error: import('packages/logger/dist/lib/logger').MethodHandler<import('packages/logger/dist/lib/logger').SupportedOutput>;
3
+ warn: import('packages/logger/dist/lib/logger').MethodHandler<import('packages/logger/dist/lib/logger').SupportedOutput>;
4
+ success: import('packages/logger/dist/lib/logger').MethodHandler<import('packages/logger/dist/lib/logger').SupportedOutput>;
5
+ info: import('packages/logger/dist/lib/logger').MethodHandler<import('packages/logger/dist/lib/logger').SupportedOutput>;
6
+ verbose: import('packages/logger/dist/lib/logger').MethodHandler<import('packages/logger/dist/lib/logger').SupportedOutput>;
7
+ debug: import('packages/logger/dist/lib/logger').MethodHandler<import('packages/logger/dist/lib/logger').SupportedOutput>;
8
8
  }>;
9
9
  //# sourceMappingURL=logger.d.ts.map
@@ -0,0 +1,12 @@
1
+ /**
2
+ * A directory whose name ends with a single underscore (e.g. `features_`) is
3
+ * opaque: a transparent container that doesn't count toward the folder
4
+ * structure depth.
5
+ *
6
+ * The single-trailing-underscore rule deliberately spares names like
7
+ * `__tests__`, where the trailing underscore is part of the convention rather
8
+ * than an opacity marker.
9
+ */
10
+ export declare function isOpaqueSegment(segment: string): boolean;
11
+ export declare function stripOpaqueSegments(segments: string[]): string[];
12
+ //# sourceMappingURL=opaqueSegments.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"opaqueSegments.d.ts","sourceRoot":"","sources":["../../src/lib/opaqueSegments.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAExD;AAED,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,CAEhE"}
@@ -0,0 +1,3 @@
1
+ "use strict";const x=require("chalk"),P=require("dependency-cruiser"),y=require("dependency-cruiser/config-utl/extract-depcruise-options"),$=require("dependency-cruiser/config-utl/extract-ts-config"),q=require("dependency-cruiser/config-utl/extract-webpack-resolve-config"),F=require("@leancodepl/logger/cli");function g(e){const[t,...s]=e;if(!t)return[];if(s.length===0)return t;const o=[],n=Math.min(...e.map(r=>r.length));for(let r=0;r<n;r++){const i=t[r];if(i===void 0)break;if(e.every(u=>u[r]===i))o.push(i);else break}return o}function p(e){return g(e).length}function M(e){return/[^_]_$/.test(e)}function d(e){return e.filter(t=>!M(t))}function j(e){const t=typeof e.output=="object"?e.output:void 0,s=t?.modules??[],o=[];for(const n of s){if(n.coreModule)continue;const r=n.dependencies||[],i=d(n.source.split("/"));r.forEach(u=>{if(b(u.dependencyTypes))return;const l=d(u.resolved.split("/")),c=p([i,l]);(!c||c<i.length&&l.length>c+k)&&o.push({source:n.source,target:u.resolved,rule:"cross-feature-nested-imports",severity:"error"})})}return{messages:o,totalCruised:t?.summary.totalCruised??0}}const k=2;function b(e){return e?.some(t=>t==="core"||t==="unknown"||t.startsWith("npm"))??!1}const{bold:m}=x;function h(e){return e.map(t=>`${t.rule}: ${m(t.source)} → ${m(t.target)}`)}async function C({directories:e=[".*"],configPath:t,tsConfigPath:s,webpackConfigPath:o}){const n=await y(t),r=o?await q(o):void 0,i=s?$(s):void 0;return await P.cruise(e,{...n},r,{tsConfig:i})}const a=F.createCliLogger();async function w(e){const t=await C(e),{messages:s,totalCruised:o}=j(t);if(s.length===0&&a.success("✅ No issues found!"),s.length>0){const n=h(s);a.error(n.join(`
2
+ `)),a.error(`Found ${s.length} violation(s). ${o} modules cruised.`)}return s.length}function L(e,t,s){const o=e.at(-1)??"";if(!/index\.(tsx?|jsx?|ts|js)$/.test(o))return!1;const n=e.at(-3)??"",r=t.at(-1);return n===r&&s===e.length-2}function R(e){const t=typeof e.output=="object"?e.output:void 0,s=t?.modules??[],o=[];for(const n of s){if(n.coreModule)continue;const r=n.dependents||[];if(r.length<=1)continue;const i=d(n.source.split("/")),u=i.length;if(u<=2)continue;const l=r.map(v=>d(v.split("/"))),c=g(l),f=p([i,c]);L(i,c,f)||f<u-1&&o.push({source:n.source,target:c.join("/"),rule:"not-shared-level",severity:"info"})}return{messages:o,totalCruised:t?.summary.totalCruised??0}}async function S(e){const t=await C(e),{messages:s,totalCruised:o}=R(t);if(s.length===0&&a.success("✅ No issues found!"),s.length>0){const n=h(s);a.info(n.join(`
3
+ `)),a.info(`Found ${s.length} violation(s). ${o} modules cruised.`)}return 0}exports.logger=a;exports.validateCrossFeatureImports=w;exports.validateSharedComponent=S;
@@ -0,0 +1,127 @@
1
+ import C from "chalk";
2
+ import { cruise as P } from "dependency-cruiser";
3
+ import y from "dependency-cruiser/config-utl/extract-depcruise-options";
4
+ import $ from "dependency-cruiser/config-utl/extract-ts-config";
5
+ import M from "dependency-cruiser/config-utl/extract-webpack-resolve-config";
6
+ import { createCliLogger as j } from "@leancodepl/logger/cli";
7
+ function p(e) {
8
+ const [t, ...o] = e;
9
+ if (!t) return [];
10
+ if (o.length === 0) return t;
11
+ const n = [], s = Math.min(...e.map((r) => r.length));
12
+ for (let r = 0; r < s; r++) {
13
+ const i = t[r];
14
+ if (i === void 0) break;
15
+ if (e.every((u) => u[r] === i))
16
+ n.push(i);
17
+ else
18
+ break;
19
+ }
20
+ return n;
21
+ }
22
+ function g(e) {
23
+ return p(e).length;
24
+ }
25
+ function k(e) {
26
+ return /[^_]_$/.test(e);
27
+ }
28
+ function l(e) {
29
+ return e.filter((t) => !k(t));
30
+ }
31
+ function F(e) {
32
+ const t = typeof e.output == "object" ? e.output : void 0, o = t?.modules ?? [], n = [];
33
+ for (const s of o) {
34
+ if (s.coreModule)
35
+ continue;
36
+ const r = s.dependencies || [], i = l(s.source.split("/"));
37
+ r.forEach((u) => {
38
+ if (w(u.dependencyTypes))
39
+ return;
40
+ const f = l(u.resolved.split("/")), c = g([i, f]);
41
+ (!c || c < i.length && f.length > c + b) && n.push({
42
+ source: s.source,
43
+ target: u.resolved,
44
+ rule: "cross-feature-nested-imports",
45
+ severity: "error"
46
+ });
47
+ });
48
+ }
49
+ return {
50
+ messages: n,
51
+ totalCruised: t?.summary.totalCruised ?? 0
52
+ };
53
+ }
54
+ const b = 2;
55
+ function w(e) {
56
+ return e?.some((t) => t === "core" || t === "unknown" || t.startsWith("npm")) ?? !1;
57
+ }
58
+ const { bold: d } = C;
59
+ function h(e) {
60
+ return e.map((t) => `${t.rule}: ${d(t.source)} → ${d(t.target)}`);
61
+ }
62
+ async function v({
63
+ directories: e = [".*"],
64
+ configPath: t,
65
+ tsConfigPath: o,
66
+ webpackConfigPath: n
67
+ }) {
68
+ const s = await y(t), r = n ? await M(n) : void 0, i = o ? $(o) : void 0;
69
+ return await P(e, { ...s }, r, {
70
+ tsConfig: i
71
+ });
72
+ }
73
+ const a = j();
74
+ async function E(e) {
75
+ const t = await v(e), { messages: o, totalCruised: n } = F(t);
76
+ if (o.length === 0 && a.success("✅ No issues found!"), o.length > 0) {
77
+ const s = h(o);
78
+ a.error(s.join(`
79
+ `)), a.error(`Found ${o.length} violation(s). ${n} modules cruised.`);
80
+ }
81
+ return o.length;
82
+ }
83
+ function L(e, t, o) {
84
+ const n = e.at(-1) ?? "";
85
+ if (!/index\.(tsx?|jsx?|ts|js)$/.test(n))
86
+ return !1;
87
+ const s = e.at(-3) ?? "", r = t.at(-1);
88
+ return s === r && o === e.length - 2;
89
+ }
90
+ function R(e) {
91
+ const t = typeof e.output == "object" ? e.output : void 0, o = t?.modules ?? [], n = [];
92
+ for (const s of o) {
93
+ if (s.coreModule)
94
+ continue;
95
+ const r = s.dependents || [];
96
+ if (r.length <= 1)
97
+ continue;
98
+ const i = l(s.source.split("/")), u = i.length;
99
+ if (u <= 2)
100
+ continue;
101
+ const f = r.map((x) => l(x.split("/"))), c = p(f), m = g([i, c]);
102
+ L(i, c, m) || m < u - 1 && n.push({
103
+ source: s.source,
104
+ target: c.join("/"),
105
+ rule: "not-shared-level",
106
+ severity: "info"
107
+ });
108
+ }
109
+ return {
110
+ messages: n,
111
+ totalCruised: t?.summary.totalCruised ?? 0
112
+ };
113
+ }
114
+ async function T(e) {
115
+ const t = await v(e), { messages: o, totalCruised: n } = R(t);
116
+ if (o.length === 0 && a.success("✅ No issues found!"), o.length > 0) {
117
+ const s = h(o);
118
+ a.info(s.join(`
119
+ `)), a.info(`Found ${o.length} violation(s). ${n} modules cruised.`);
120
+ }
121
+ return 0;
122
+ }
123
+ export {
124
+ T as a,
125
+ a as l,
126
+ E as v
127
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@leancodepl/folder-structure-cruiser",
3
- "version": "10.3.1",
3
+ "version": "10.5.0",
4
4
  "license": "Apache-2.0",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -19,7 +19,7 @@
19
19
  "folder-structure-cruiser": "./dist/bin.js"
20
20
  },
21
21
  "dependencies": {
22
- "@leancodepl/logger": "10.3.1",
22
+ "@leancodepl/logger": "10.5.0",
23
23
  "chalk": ">=5.0.0",
24
24
  "commander": "^14.0.0"
25
25
  },
@@ -1,113 +0,0 @@
1
- import C from "chalk";
2
- import { cruise as x } from "dependency-cruiser";
3
- import P from "dependency-cruiser/config-utl/extract-depcruise-options";
4
- import y from "dependency-cruiser/config-utl/extract-ts-config";
5
- import M from "dependency-cruiser/config-utl/extract-webpack-resolve-config";
6
- import { createCliLogger as $ } from "@leancodepl/logger/cli";
7
- function m(e) {
8
- if (e.length === 0) return [];
9
- if (e.length === 1) return e[0];
10
- const s = [], t = Math.min(...e.map((o) => o.length));
11
- for (let o = 0; o < t; o++) {
12
- const n = e[0][o];
13
- if (e.every((r) => r[o] === n))
14
- s.push(n);
15
- else
16
- break;
17
- }
18
- return s;
19
- }
20
- function g(e) {
21
- return m(e).length;
22
- }
23
- function j(e) {
24
- const s = typeof e.output == "object" ? e.output : void 0, t = s?.modules ?? [], o = [];
25
- for (const n of t) {
26
- if (n.coreModule)
27
- continue;
28
- const r = n.dependencies || [], i = n.source.split("/");
29
- r.forEach((u) => {
30
- const l = u.resolved.split("/"), c = g([i, l]);
31
- (!c || c < i.length && l.length > c + 2) && o.push({
32
- source: n.source,
33
- target: u.resolved,
34
- rule: "cross-feature-nested-imports",
35
- severity: "error"
36
- });
37
- });
38
- }
39
- return {
40
- messages: o,
41
- totalCruised: s?.summary.totalCruised ?? 0
42
- };
43
- }
44
- const { bold: d } = C;
45
- function p(e) {
46
- return e.map((s) => `${s.rule}: ${d(s.source)} → ${d(s.target)}`);
47
- }
48
- async function h({
49
- directories: e = [".*"],
50
- configPath: s,
51
- tsConfigPath: t,
52
- webpackConfigPath: o
53
- }) {
54
- const n = await P(s), r = o ? await M(o) : void 0, i = t ? y(t) : void 0;
55
- return await x(e, { ...n }, r, {
56
- tsConfig: i
57
- });
58
- }
59
- const a = $();
60
- async function S(e) {
61
- const s = await h(e), { messages: t, totalCruised: o } = j(s);
62
- if (t.length === 0 && a.success("✅ No issues found!"), t.length > 0) {
63
- const n = p(t);
64
- a.error(n.join(`
65
- `)), a.error(`Found ${t.length} violation(s). ${o} modules cruised.`);
66
- }
67
- return t.length;
68
- }
69
- function F(e, s, t) {
70
- const o = e.at(-1) ?? "";
71
- if (!/index\.(tsx?|jsx?|ts|js)$/.test(o))
72
- return !1;
73
- const n = e.at(-3) ?? "", r = s.at(-1);
74
- return n === r && t === e.length - 2;
75
- }
76
- function L(e) {
77
- const s = typeof e.output == "object" ? e.output : void 0, t = s?.modules ?? [], o = [];
78
- for (const n of t) {
79
- if (n.coreModule)
80
- continue;
81
- const r = n.dependents || [];
82
- if (r.length <= 1)
83
- continue;
84
- const i = n.source.split("/"), u = i.length;
85
- if (u <= 2)
86
- continue;
87
- const l = r.map((v) => v.split("/")), c = m(l), f = g([i, c]);
88
- F(i, c, f) || f < u - 1 && o.push({
89
- source: n.source,
90
- target: c.join("/"),
91
- rule: "not-shared-level",
92
- severity: "info"
93
- });
94
- }
95
- return {
96
- messages: o,
97
- totalCruised: s?.summary.totalCruised ?? 0
98
- };
99
- }
100
- async function D(e) {
101
- const s = await h(e), { messages: t, totalCruised: o } = L(s);
102
- if (t.length === 0 && a.success("✅ No issues found!"), t.length > 0) {
103
- const n = p(t);
104
- a.info(n.join(`
105
- `)), a.info(`Found ${t.length} violation(s). ${o} modules cruised.`);
106
- }
107
- return t.length;
108
- }
109
- export {
110
- D as a,
111
- a as l,
112
- S as v
113
- };
@@ -1,3 +0,0 @@
1
- "use strict";const v=require("chalk"),x=require("dependency-cruiser"),P=require("dependency-cruiser/config-utl/extract-depcruise-options"),y=require("dependency-cruiser/config-utl/extract-ts-config"),F=require("dependency-cruiser/config-utl/extract-webpack-resolve-config"),M=require("@leancodepl/logger/cli");function g(e){if(e.length===0)return[];if(e.length===1)return e[0];const s=[],t=Math.min(...e.map(o=>o.length));for(let o=0;o<t;o++){const n=e[0][o];if(e.every(r=>r[o]===n))s.push(n);else break}return s}function m(e){return g(e).length}function $(e){const s=typeof e.output=="object"?e.output:void 0,t=s?.modules??[],o=[];for(const n of t){if(n.coreModule)continue;const r=n.dependencies||[],i=n.source.split("/");r.forEach(a=>{const l=a.resolved.split("/"),c=m([i,l]);(!c||c<i.length&&l.length>c+2)&&o.push({source:n.source,target:a.resolved,rule:"cross-feature-nested-imports",severity:"error"})})}return{messages:o,totalCruised:s?.summary.totalCruised??0}}const{bold:f}=v;function p(e){return e.map(s=>`${s.rule}: ${f(s.source)} → ${f(s.target)}`)}async function h({directories:e=[".*"],configPath:s,tsConfigPath:t,webpackConfigPath:o}){const n=await P(s),r=o?await F(o):void 0,i=t?y(t):void 0;return await x.cruise(e,{...n},r,{tsConfig:i})}const u=M.createCliLogger();async function j(e){const s=await h(e),{messages:t,totalCruised:o}=$(s);if(t.length===0&&u.success("✅ No issues found!"),t.length>0){const n=p(t);u.error(n.join(`
2
- `)),u.error(`Found ${t.length} violation(s). ${o} modules cruised.`)}return t.length}function q(e,s,t){const o=e.at(-1)??"";if(!/index\.(tsx?|jsx?|ts|js)$/.test(o))return!1;const n=e.at(-3)??"",r=s.at(-1);return n===r&&t===e.length-2}function L(e){const s=typeof e.output=="object"?e.output:void 0,t=s?.modules??[],o=[];for(const n of t){if(n.coreModule)continue;const r=n.dependents||[];if(r.length<=1)continue;const i=n.source.split("/"),a=i.length;if(a<=2)continue;const l=r.map(C=>C.split("/")),c=g(l),d=m([i,c]);q(i,c,d)||d<a-1&&o.push({source:n.source,target:c.join("/"),rule:"not-shared-level",severity:"info"})}return{messages:o,totalCruised:s?.summary.totalCruised??0}}async function R(e){const s=await h(e),{messages:t,totalCruised:o}=L(s);if(t.length===0&&u.success("✅ No issues found!"),t.length>0){const n=p(t);u.info(n.join(`
3
- `)),u.info(`Found ${t.length} violation(s). ${o} modules cruised.`)}return t.length}exports.logger=u;exports.validateCrossFeatureImports=j;exports.validateSharedComponent=R;