dependency-cruiser 18.0.0-beta-1 → 18.0.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dependency-cruiser",
3
- "version": "18.0.0-beta-1",
3
+ "version": "18.0.0",
4
4
  "description": "Validate and visualize dependencies. With your rules. JavaScript, TypeScript, CoffeeScript. ES6, CommonJS, AMD.",
5
5
  "keywords": [
6
6
  "static analysis",
@@ -143,13 +143,13 @@
143
143
  "README.md"
144
144
  ],
145
145
  "dependencies": {
146
- "acorn": "8.16.0",
146
+ "acorn": "8.17.0",
147
147
  "acorn-jsx": "5.3.2",
148
148
  "acorn-jsx-walk": "2.0.0",
149
149
  "acorn-loose": "8.5.2",
150
150
  "acorn-walk": "8.3.5",
151
151
  "commander": "15.0.0",
152
- "enhanced-resolve": "5.22.1",
152
+ "enhanced-resolve": "5.24.1",
153
153
  "ignore": "7.0.5",
154
154
  "interpret": "3.1.1",
155
155
  "is-installed-globally": "1.0.0",
@@ -158,8 +158,9 @@
158
158
  "prompts": "2.4.2",
159
159
  "rechoir": "0.8.0",
160
160
  "safe-regex": "2.1.1",
161
- "semver": "7.8.1",
162
- "watskeburt": "5.0.3"
161
+ "semver": "7.8.5",
162
+ "tsconfig-paths-webpack-plugin": "4.2.0",
163
+ "watskeburt": "6.0.0"
163
164
  },
164
165
  "engines": {
165
166
  "node": "^22||^24||>=26"
@@ -1,7 +1,6 @@
1
1
  import { readFile } from "node:fs/promises";
2
2
 
3
3
  import { extname } from "node:path";
4
- import json5 from "json5";
5
4
  import makeAbsolute from "./make-absolute.mjs";
6
5
  import tryImport from "#utl/try-import.mjs";
7
6
  import meta from "#meta.cjs";
@@ -36,6 +35,7 @@ async function getJSConfig(pBabelConfigFileName) {
36
35
 
37
36
  async function getJSON5Config(pBabelConfigFileName) {
38
37
  let lReturnValue = {};
38
+ const { default: json5 } = await import("json5");
39
39
 
40
40
  try {
41
41
  lReturnValue = json5.parse(await readFile(pBabelConfigFileName, "utf8"));
@@ -95,7 +95,7 @@ export default async function extractBabelConfig(pBabelConfigFileName) {
95
95
  filename: pBabelConfigFileName,
96
96
  };
97
97
  lReturnValue = {
98
- ...babel.loadOptions(lConfig),
98
+ ...babel.loadOptionsSync(lConfig),
99
99
  // according to the babel documentation a config parsed & expanded through
100
100
  // loadOptions can be passed to the parser. With some plugins/ presets
101
101
  // this does not seem to be true anymore, though
@@ -58,7 +58,7 @@ export default async function extractDepcruiseConfig(
58
58
  const lResolvedFileName = resolve(
59
59
  pConfigFileName,
60
60
  pBaseDirectory,
61
- normalizeResolveOptions(
61
+ await normalizeResolveOptions(
62
62
  {
63
63
  extensions: [".js", ".json", ".cjs", ".mjs"],
64
64
  },
@@ -1,6 +1,5 @@
1
1
  import { readFile } from "node:fs/promises";
2
2
  import { extname } from "node:path";
3
- import json5 from "json5";
4
3
 
5
4
  /**
6
5
  *
@@ -16,5 +15,6 @@ export default async function readConfig(pAbsolutePathToConfigFile) {
16
15
  );
17
16
  return config;
18
17
  }
18
+ const { default: json5 } = await import("json5");
19
19
  return json5.parse(await readFile(pAbsolutePathToConfigFile, "utf8"));
20
20
  }
@@ -1,5 +1,4 @@
1
1
  import { readFile } from "node:fs/promises";
2
- import json5 from "json5";
3
2
  import makeAbsolute from "./make-absolute.mjs";
4
3
 
5
4
  /**
@@ -53,6 +52,8 @@ function makeForwardCompatible(pKnownViolation) {
53
52
  }
54
53
 
55
54
  export default async function extractKnownViolations(pKnownViolationsFileName) {
55
+ const { default: json5 } = await import("json5");
56
+
56
57
  try {
57
58
  const lFileContents = await readFile(
58
59
  makeAbsolute(pKnownViolationsFileName),
@@ -1,6 +1,6 @@
1
- import { resolve as resolvePath } from "node:path";
2
1
  import enhancedResolve from "enhanced-resolve";
3
2
  import { stripQueryParameters } from "../helpers.mjs";
3
+ import pathToPosix from "#utl/path-to-posix.mjs";
4
4
 
5
5
  /** @import {IResolveOptions} from "../../../types/resolve-options.mjs" */
6
6
 
@@ -50,10 +50,8 @@ export function resolve(
50
50
  return stripQueryParameters(
51
51
  gResolvers.get(pCachingContext).resolveSync(
52
52
  {},
53
- // lookupStartPath - must be absolute so enhanced-resolve classifies
54
- // it correctly on Windows (relative paths with backslashes would be
55
- // treated as PathType.Normal and POSIX-normalised, breaking ".." resolution)
56
- resolvePath(pFileDirectory),
53
+ // lookupStartPath
54
+ pathToPosix(pFileDirectory),
57
55
  // request
58
56
  pModuleName,
59
57
  ),
@@ -1,3 +1,4 @@
1
+ /** @import { IViolation } from "../../types/dependency-cruiser.mjs" */
1
2
  /* eslint-disable no-magic-numbers */
2
3
  function severity2number(pSeverity) {
3
4
  const lSeverity2Number = new Map([
@@ -16,6 +17,63 @@ export function compareSeverities(pFirstSeverity, pSecondSeverity) {
16
17
  );
17
18
  }
18
19
 
20
+ /**
21
+ * Compare two arrays of mini dependencies by their 'name' field.
22
+ * Used to deterministically order violations that have the same severity/rule/from/to
23
+ * but differ in their cycle or via paths.
24
+ *
25
+ * @param {Array<{name: string}>} pFirstArray - First array (or undefined)
26
+ * @param {Array<{name: string}>} pSecondArray - Second array (or undefined)
27
+ * @returns {number} - -1/0/1 following comparison semantics
28
+ */
29
+ function compareArraysByName(pFirstArray, pSecondArray) {
30
+ const lFirst = pFirstArray || [];
31
+ const lSecond = pSecondArray || [];
32
+ const lMinLength = Math.min(lFirst.length, lSecond.length);
33
+
34
+ // eslint-disable-next-line unicorn/prevent-abbreviations, no-plusplus
35
+ for (let i = 0; i < lMinLength; i++) {
36
+ // eslint-disable-next-line security/detect-object-injection
37
+ const lComparison = lFirst[i].name.localeCompare(lSecond[i].name);
38
+ if (lComparison !== 0) {
39
+ return lComparison;
40
+ }
41
+ }
42
+
43
+ return Math.sign(lFirst.length - lSecond.length);
44
+ }
45
+
46
+ /**
47
+ * Compare two arrays of strings.
48
+ *
49
+ * @param {Array<string>} pFirstArray - First array (or undefined)
50
+ * @param {Array<string>} pSecondArray - Second array (or undefined)
51
+ * @returns {number} - -1/0/1 following comparison semantics
52
+ */
53
+ function compareArrays(pFirstArray, pSecondArray) {
54
+ const lFirst = pFirstArray || [];
55
+ const lSecond = pSecondArray || [];
56
+ const lMinLength = Math.min(lFirst.length, lSecond.length);
57
+
58
+ // eslint-disable-next-line unicorn/prevent-abbreviations, no-plusplus
59
+ for (let i = 0; i < lMinLength; i++) {
60
+ // eslint-disable-next-line security/detect-object-injection
61
+ const lComparison = lFirst[i].localeCompare(lSecond[i]);
62
+ if (lComparison !== 0) {
63
+ return lComparison;
64
+ }
65
+ }
66
+
67
+ return Math.sign(lFirst.length - lSecond.length);
68
+ }
69
+
70
+ /**
71
+ *
72
+ * @param {IViolation} pFirstViolation
73
+ * @param {IViolation} pSecondViolation
74
+ * @returns
75
+ */
76
+ // eslint-disable-next-line complexity
19
77
  export function compareViolations(pFirstViolation, pSecondViolation) {
20
78
  return (
21
79
  compareSeverities(
@@ -24,7 +82,17 @@ export function compareViolations(pFirstViolation, pSecondViolation) {
24
82
  ) ||
25
83
  pFirstViolation.rule.name.localeCompare(pSecondViolation.rule.name) ||
26
84
  pFirstViolation.from.localeCompare(pSecondViolation.from) ||
27
- pFirstViolation.to.localeCompare(pSecondViolation.to)
85
+ pFirstViolation.to.localeCompare(pSecondViolation.to) ||
86
+ (pFirstViolation.unresolvedTo ?? "").localeCompare(
87
+ pSecondViolation.unresolvedTo ?? "",
88
+ ) ||
89
+ (pFirstViolation.type ?? "").localeCompare(pSecondViolation.type ?? "") ||
90
+ compareArrays(
91
+ pFirstViolation.dependencyTypes,
92
+ pSecondViolation.dependencyTypes,
93
+ ) ||
94
+ compareArraysByName(pFirstViolation.cycle, pSecondViolation.cycle) ||
95
+ compareArraysByName(pFirstViolation.via, pSecondViolation.via)
28
96
  );
29
97
  }
30
98
 
@@ -78,7 +78,7 @@ export default async function cruise(
78
78
  );
79
79
 
80
80
  bus.summary("startup: get resolve options", c(5));
81
- const lNormalizedResolveOptions = normalizeResolveOptions(
81
+ const lNormalizedResolveOptions = await normalizeResolveOptions(
82
82
  pResolveOptions,
83
83
  lCruiseOptions,
84
84
  pTranspileOptions?.tsConfig,
@@ -1,5 +1,4 @@
1
1
  import fs from "node:fs";
2
- import { resolve as resolvePath } from "node:path";
3
2
  import enhancedResolve from "enhanced-resolve";
4
3
  import { scannableExtensions } from "#extract/transpile/meta.mjs";
5
4
  import {
@@ -62,20 +61,64 @@ function getNonOverridableResolveOptions(pCacheDuration) {
62
61
  };
63
62
  }
64
63
 
65
- function compileResolveOptions(
64
+ function pushPlugin(pPlugins, pPluginToPush) {
65
+ return (pPlugins || []).concat(pPluginToPush);
66
+ }
67
+
68
+ // eslint-disable-next-line max-lines-per-function
69
+ async function compileResolveOptions(
66
70
  pResolveOptions,
67
71
  pTSConfig,
68
72
  pResolveOptionsFromDCConfig,
69
73
  ) {
70
74
  let lResolveOptions = {};
71
75
 
76
+ // There's a performance impact of ~1 ms per resolve even when there
77
+ // are 0 paths in the tsconfig, so not loading it when not necessary
78
+ // will be a win.
79
+ // Also: requiring the plugin only when it's necessary will save some
80
+ // startup time (especially on a cold require cache)
72
81
  if (pResolveOptions.tsConfig) {
73
- lResolveOptions.tsconfig = {
74
- configFile: resolvePath(pResolveOptions.tsConfig),
75
-
76
- // baseUrl: pTSConfig?.options?.baseUrl ? undefined : "./",
77
- // references: pTSConfig?.options?.references ?? []
78
- };
82
+ const { default: TsConfigPathsPlugin } =
83
+ await import("tsconfig-paths-webpack-plugin");
84
+ lResolveOptions.plugins = pushPlugin(
85
+ lResolveOptions.plugins,
86
+ // @ts-expect-error TS2351 "TsConfPathsPlugin is not constructable" - is unjustified
87
+ new TsConfigPathsPlugin({
88
+ configFile: pResolveOptions.tsConfig,
89
+ // TsConfigPathsPlugin requires a baseUrl to be present in the tsconfig,
90
+ // otherwise it prints scary messages that it didn't and read the tsConfig
91
+ // (potentially making users think it's dependency-cruiser disregarding the
92
+ // tsconfig). Hence up till version 13.0.4 dependency-cruiser only loaded
93
+ // TsConfigPathsPlugin when an options.baseUrl existed. However, this
94
+ // isn't necessary anymore:
95
+ // - [tsconfig#baseUrl documentation](https://www.typescriptlang.org/tsconfig#baseUrl)
96
+ // UNrecommends the use of the baseUrl for non-AMD projects
97
+ // - [tsconfig-paths PR #207](https://github.com/dividab/tsconfig-paths/pull/208)
98
+ // 'tolerates' undefined baseUrls
99
+ //
100
+ // Hence, until
101
+ // [tpwp issue #99](https://github.com/dividab/tsconfig-paths-webpack-plugin/issues/99)
102
+ // lands:
103
+ // - pass a default baseUrl to TsConfigPathsPlugin if the baseUrl isn't available
104
+ // - pass undefined in all other cases; TsConfigPathsPlugin will read
105
+ // it from the tsconfig.json in that case. Passing the processed baseUrl
106
+ // (pTSConfig?.options?.baseUrl) instead would've been more obvious, but
107
+ // doesn't work, as that is an absolute path and tsconfig-paths(-wpp)
108
+ // seems to process that again resulting in invalid paths and unresolved
109
+ // or erroneous dependencies
110
+ // eslint-disable-next-line no-undefined
111
+ baseUrl: pTSConfig?.options?.baseUrl ? undefined : "./",
112
+ // TsConfigPathsPlugin doesn't (can't) read enhanced-resolve's
113
+ // list of extensions, and the default it uses for extensions
114
+ // so we do it ourselves - either with the extensions passed
115
+ // or with the supported ones.
116
+ extensions:
117
+ pResolveOptionsFromDCConfig.extensions ||
118
+ pResolveOptions.extensions ||
119
+ DEFAULT_RESOLVE_OPTIONS.extensions,
120
+ }),
121
+ );
79
122
  }
80
123
 
81
124
  return {
@@ -97,14 +140,14 @@ function compileResolveOptions(
97
140
  * @returns
98
141
  */
99
142
  // eslint-disable-next-line complexity
100
- export default function normalizeResolveOptions(
143
+ export default async function normalizeResolveOptions(
101
144
  pResolveOptions,
102
145
  pOptions,
103
146
  pTSConfig,
104
147
  ) {
105
148
  const lRuleSet = pOptions?.ruleSet ?? {};
106
149
 
107
- return compileResolveOptions(
150
+ return await compileResolveOptions(
108
151
  {
109
152
  // EnhancedResolve's symlinks:
110
153
  // - true => symlinks are followed (vv)
package/src/meta.cjs CHANGED
@@ -1,6 +1,6 @@
1
1
  /* generated - don't edit */
2
2
  module.exports = {
3
- version: "18.0.0-beta-1",
3
+ version: "18.0.0",
4
4
  engines: {
5
5
  node: "^22||^24||>=26",
6
6
  },