dependency-cruiser 16.1.0 → 16.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -42,7 +42,7 @@ a `.dependency-cruiser.js` configuration file attuned to your project[^1][^2].
42
42
  `npx`.
43
43
 
44
44
  [^2]:
45
- If you don't don't want to use `npx`, but instead `pnpx` (from the `pnpm`
45
+ If you don't want to use `npx`, but instead `pnpx` (from the `pnpm`
46
46
  package manager) or `yarn` - please refer to that tool's documentation.
47
47
  Particularly `pnpx` has semantics that differ from `npx` quite significantly
48
48
  and that you want to be aware of before using it. In the mean time: `npx`
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import { EOL } from "node:os";
3
- import { program } from "commander";
3
+ import { program, Option } from "commander";
4
4
  import assertNodeEnvironmentSuitable from "#cli/assert-node-environment-suitable.mjs";
5
5
  import cli from "#cli/index.mjs";
6
6
  import meta from "#meta.js";
@@ -22,7 +22,7 @@ try {
22
22
  true,
23
23
  )
24
24
  .addOption(
25
- new program.Option(
25
+ new Option(
26
26
  "--no-config",
27
27
  "do not use a configuration file. " +
28
28
  "Overrides any --config option set earlier",
@@ -34,7 +34,7 @@ try {
34
34
  "err",
35
35
  )
36
36
  .option("-m, --metrics", "calculate stability metrics", false)
37
- .addOption(new program.Option("--no-metrics").hideHelp(true))
37
+ .addOption(new Option("--no-metrics").hideHelp(true))
38
38
  .option(
39
39
  "-f, --output-to <file>",
40
40
  "file to write output to; - for stdout",
@@ -71,21 +71,21 @@ try {
71
71
  "--ignore-known [file]",
72
72
  "ignore known violations as saved in [file] (default: .dependency-cruiser-known-violations.json)",
73
73
  )
74
- .addOption(new program.Option("--no-ignore-known").hideHelp(true))
74
+ .addOption(new Option("--no-ignore-known").hideHelp(true))
75
75
  .addOption(
76
- new program.Option(
76
+ new Option(
77
77
  "--ts-config [file]",
78
78
  "use a TypeScript configuration (e.g. tsconfig.json) or it's JavaScript counterpart (e.g. jsconfig.json)",
79
79
  ).hideHelp(true),
80
80
  )
81
81
  .addOption(
82
- new program.Option(
82
+ new Option(
83
83
  "--webpack-config [file]",
84
84
  "use a webpack configuration (e.g. webpack.config.js)",
85
85
  ).hideHelp(true),
86
86
  )
87
87
  .addOption(
88
- new program.Option(
88
+ new Option(
89
89
  "--ts-pre-compilation-deps",
90
90
  "detect dependencies that only exist before typescript-to-javascript " +
91
91
  "compilation (off by default)",
@@ -98,25 +98,23 @@ try {
98
98
  "modules/ folders directly under your packages folder. ",
99
99
  )
100
100
  .addOption(
101
- new program.Option(
101
+ new Option(
102
102
  "-p, --progress [type]",
103
103
  "show progress while dependency-cruiser is busy",
104
104
  ).choices(["cli-feedback", "performance-log", "ndjson", "none"]),
105
105
  )
106
106
  .addOption(
107
- new program.Option("--no-progress", "Alias of --progress none").hideHelp(
108
- true,
109
- ),
107
+ new Option("--no-progress", "Alias of --progress none").hideHelp(true),
110
108
  )
111
109
  .addOption(
112
- new program.Option(
110
+ new Option(
113
111
  "-d, --max-depth <n>",
114
- "You probably want to use --collapse instead of --max-depth. " +
112
+ "you probably want to use --collapse instead of --max-depth. " +
115
113
  "(max-depth would limit the cruise depth; 0 <= n <= 99 (default: 0 - no limit)).",
116
114
  ).hideHelp(true),
117
115
  )
118
116
  .addOption(
119
- new program.Option(
117
+ new Option(
120
118
  "-M, --module-systems <items>",
121
119
  "list of module systems (default: amd, cjs, es6, tsd)",
122
120
  ).hideHelp(true),
@@ -127,33 +125,30 @@ try {
127
125
  )
128
126
  .option(
129
127
  "-C, --cache [cache-directory]",
130
- "(experimental) use a cache to speed up execution. " +
128
+ "use a cache to speed up execution. " +
131
129
  "The directory defaults to node_modules/.cache/dependency-cruiser",
132
130
  )
133
131
  .addOption(
134
- new program.Option(
132
+ new Option(
135
133
  "--cache-strategy <strategy>",
136
- "(experimental) strategy to use for detecting changed files in the cache.",
134
+ "strategy to use for detecting changed files in the cache.",
137
135
  ).choices(["metadata", "content"]),
138
136
  )
139
137
  .addOption(
140
- new program.Option(
138
+ new Option(
141
139
  "--no-cache",
142
140
  "switch off caching. Overrides the 'cache' key in .dependency-cruiser.js " +
143
141
  "and --cache options set earlier on the command line",
144
142
  ).hideHelp(true),
145
143
  )
146
144
  .addOption(
147
- new program.Option(
145
+ new Option(
148
146
  "--preserve-symlinks",
149
147
  "leave symlinks unchanged (off by default)",
150
148
  ).hideHelp(true),
151
149
  )
152
150
  .addOption(
153
- new program.Option(
154
- "-v, --validate [file]",
155
- `alias for --config`,
156
- ).hideHelp(true),
151
+ new Option("-v, --validate [file]", `alias for --config`).hideHelp(true),
157
152
  )
158
153
  .option(
159
154
  "-i, --info",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dependency-cruiser",
3
- "version": "16.1.0",
3
+ "version": "16.2.1",
4
4
  "description": "Validate and visualize dependencies. With your rules. JavaScript, TypeScript, CoffeeScript. ES6, CommonJS, AMD.",
5
5
  "keywords": [
6
6
  "static analysis",
@@ -16,15 +16,48 @@
16
16
  "validation",
17
17
  "spelunking"
18
18
  ],
19
- "author": {
20
- "name": "Sander Verweij",
21
- "url": "https://sverweij.github.io"
22
- },
19
+ "author": "Sander Verweij (https://sverweij.github.io)",
23
20
  "contributors": [
24
- {
25
- "name": "Klaus Meinhardt",
26
- "url": "https://github.com/ajafff"
27
- }
21
+ "Hirotaka Miyagi (https://mh4gf.dev)",
22
+ "TruongSinh Tran-Nguyen (https://truongsinh.pro)",
23
+ "Bastian Hess (https://github.com/bashess)",
24
+ "Álvaro Cuesta (https://github.com/alvaro-cuesta)",
25
+ "anna (https://github.com/annamooseity)",
26
+ "Radosław Kłos (https://klos.dev)",
27
+ "Joshua T (https://github.com/radiantly)",
28
+ "Ivan (https://github.com/Winner95)",
29
+ "Frieder Bluemle (https://github.com/friederbluemle)",
30
+ "davidparkagoda (https://github.com/davidparkagoda)",
31
+ "Matt Button (https://github.com/BRMatt)",
32
+ "Jon Lauridsen (https://jonlauridsen.com)",
33
+ "Klaus Meinhardt (https://github.com/ajafff)",
34
+ "cunzaizhuyi (https://github.com/cunzaizhuyi)",
35
+ "Greg Lockwood (https://github.com/greglockwood)",
36
+ "Jeremy Magland (https://github.com/magland)",
37
+ "Sebastian Landwehr (https://sebastianlandwehr.com)",
38
+ "Brody McKee (https://github.com/mrmckeb)",
39
+ "Bin (https://github.com/soulhat)",
40
+ "정건우 (https://www.zigae.com/)",
41
+ "Roy Swinkels (https://github.com/donroyco)",
42
+ "Martin Slota (https://github.com/martinslota)",
43
+ "Luke Page (https://github.com/lukeapage)",
44
+ "Emily Marigold Klassen (https://forivall.com)",
45
+ "Christian Vuerings (https://github.com/christianvuerings)",
46
+ "Yuanhai He (https://bestmike007.com)",
47
+ "Quentin de Metz (https://github.com/quentindemetz)",
48
+ "Lars Artmann (https://larsartmann.com)",
49
+ "Jessica Kerr (https://jessitron.com)",
50
+ "Creative Ataraxia (https://github.com/Creative-Ataraxia)",
51
+ "0xflotus (https://github.com/0xflotus)",
52
+ "Daniel Edholm Ignat (https://github.com/dignite)",
53
+ "Daniel Rodríguez Rivero (https://danielorodriguez.com)",
54
+ "Nick Ribal (https://github.com/elektronik2k5)",
55
+ "Richard Musiol https(://github.com/neelance)",
56
+ "Sharang Pai (https://sharangpai.me)",
57
+ "Stefan Gojan (https://stefan-gojan.de)",
58
+ "Tharun Rajendran (https://github.com/tharun208)",
59
+ "electrovir (https://github.com/electrovir)",
60
+ "fusheng (https://github.com/lin-hun)"
28
61
  ],
29
62
  "license": "MIT",
30
63
  "repository": {
@@ -106,20 +139,20 @@
106
139
  "acorn-walk": "8.3.2",
107
140
  "ajv": "8.12.0",
108
141
  "chalk": "5.3.0",
109
- "commander": "11.1.0",
142
+ "commander": "12.0.0",
110
143
  "enhanced-resolve": "5.15.0",
111
144
  "figures": "6.0.1",
112
- "ignore": "5.3.0",
145
+ "ignore": "5.3.1",
113
146
  "indent-string": "5.0.0",
114
147
  "interpret": "^3.1.1",
115
148
  "is-installed-globally": "1.0.0",
116
149
  "json5": "2.2.3",
117
150
  "lodash": "4.17.21",
118
- "picomatch": "3.0.1",
151
+ "picomatch": "4.0.1",
119
152
  "prompts": "2.4.2",
120
153
  "rechoir": "^0.8.0",
121
154
  "safe-regex": "2.1.1",
122
- "semver": "^7.5.4",
155
+ "semver": "^7.6.0",
123
156
  "semver-try-require": "6.2.3",
124
157
  "teamcity-service-messages": "0.1.14",
125
158
  "tsconfig-paths-webpack-plugin": "4.1.0",
@@ -127,11 +160,11 @@
127
160
  "wrap-ansi": "9.0.0"
128
161
  },
129
162
  "overrides": {
130
- "semver": "^7.5.4",
163
+ "semver": "^7.6.0",
131
164
  "postcss": "^8.4.31"
132
165
  },
133
166
  "resolutions": {
134
- "semver": "^7.5.4",
167
+ "semver": "^7.6.0",
135
168
  "postcss": "^8.4.31"
136
169
  },
137
170
  "engines": {
@@ -199,38 +199,31 @@ module.exports = {
199
199
  ],
200
200
  options: {
201
201
 
202
- /* conditions specifying which files not to follow further when encountered:
203
- - path: a regular expression to match
204
- - dependencyTypes: see https://github.com/sverweij/dependency-cruiser/blob/main/doc/rules-reference.md#dependencytypes-and-dependencytypesnot
205
- for a complete list
206
- */
202
+ /* Which modules not to follow further when encountered */
207
203
  doNotFollow: {
208
- path: 'node_modules'
204
+ /* path: an array of regular expressions in strings to match against */
205
+ path: ['node_modules']
209
206
  },
210
207
 
211
- /* conditions specifying which dependencies to exclude
212
- - path: a regular expression to match
213
- - dynamic: a boolean indicating whether to ignore dynamic (true) or static (false) dependencies.
214
- leave out if you want to exclude neither (recommended!)
215
- */
208
+ /* Which modules to exclude */
216
209
  // exclude : {
210
+ // /* path: an array of regular expressions in strings to match against */
217
211
  // path: '',
218
- // dynamic: true
219
212
  // },
220
213
 
221
- /* pattern specifying which files to include (regular expression)
214
+ /* Which modules to exclusively include (array of regular expressions in strings)
222
215
  dependency-cruiser will skip everything not matching this pattern
223
216
  */
224
- // includeOnly : '',
217
+ // includeOnly : [''],
225
218
 
226
219
  /* dependency-cruiser will include modules matching against the focus
227
- regular expression in its output, as well as their neighbours (direct
228
- dependencies and dependents)
220
+ regular expression in its output, as well as their direct neighbours
221
+ (dependencies and dependents)
229
222
  */
230
223
  // focus : '',
231
224
 
232
- /* List of module systems to cruise.
233
- When left out dependency-cruiser will fall back to the list of _all_
225
+ /* List of module systems to cruise.
226
+ When left out dependency-cruiser will fall back to the list of _all_
234
227
  module systems it knows of. It's the default because it's the safe option
235
228
  It might come at a performance penalty, though.
236
229
  moduleSystems: ['amd', 'cjs', 'es6', 'tsd']
@@ -245,7 +238,7 @@ module.exports = {
245
238
  to open it on your online repo or \`vscode://file/$\{process.cwd()}/\` to
246
239
  open it in visual studio code),
247
240
  */
248
- // prefix: '',
241
+ // prefix: 'vscode://file/$\{process.cwd()}/\',
249
242
 
250
243
  /* false (the default): ignore dependencies that only exist before typescript-to-javascript compilation
251
244
  true: also detect dependencies that only exist before typescript-to-javascript compilation
@@ -253,10 +246,9 @@ module.exports = {
253
246
  */
254
247
  {{tsPreCompilationDepsAttribute}}
255
248
 
256
- /*
257
- list of extensions to scan that aren't javascript or compile-to-javascript.
249
+ /* list of extensions to scan that aren't javascript or compile-to-javascript.
258
250
  Empty by default. Only put extensions in here that you want to take into
259
- account that are _not_ parsable.
251
+ account that are _not_ parsable.
260
252
  */
261
253
  // extraExtensionsToScan: [".json", ".jpg", ".png", ".svg", ".webp"],
262
254
 
@@ -293,9 +285,7 @@ module.exports = {
293
285
 
294
286
  /* Babel config ('.babelrc', '.babelrc.json', '.babelrc.json5', ...) to use
295
287
  for compilation (and whatever other naughty things babel plugins do to
296
- source code). This feature is well tested and usable, but might change
297
- behavior a bit over time (e.g. more precise results for used module
298
- systems) without dependency-cruiser getting a major version bump.
288
+ source code).
299
289
  */
300
290
  {{babelConfigAttribute}}
301
291
 
@@ -305,57 +295,37 @@ module.exports = {
305
295
  a hack.
306
296
  */
307
297
  // exoticRequireStrings: [],
298
+
308
299
  /* options to pass on to enhanced-resolve, the package dependency-cruiser
309
- uses to resolve module references to disk. You can set most of these
310
- options in a webpack.conf.js - this section is here for those
311
- projects that don't have a separate webpack config file.
300
+ uses to resolve module references to disk. The values below should be
301
+ suitable for most situations
312
302
 
313
- Note: settings in webpack.conf.js override the ones specified here.
303
+ If you use webpack: you can also set these in webpack.conf.js. The set
304
+ there will override the ones specified here.
314
305
  */
315
306
  enhancedResolveOptions: {
316
- /* List of strings to consider as 'exports' fields in package.json. Use
317
- ['exports'] when you use packages that use such a field and your environment
318
- supports it (e.g. node ^12.19 || >=14.7 or recent versions of webpack).
319
-
320
- If you have an \`exportsFields\` attribute in your webpack config, that one
321
- will have precedence over the one specified here.
322
- */
307
+ /* What to consider as an 'exports' field in package.jsons */
323
308
  exportsFields: ["exports"],
324
- /* List of conditions to check for in the exports field. e.g. use ['imports']
325
- if you're only interested in exposed es6 modules, ['require'] for commonjs,
326
- or all conditions at once \`(['import', 'require', 'node', 'default']\`)
327
- if anything goes for you. Only works when the 'exportsFields' array is
328
- non-empty.
329
-
330
- If you have a 'conditionNames' attribute in your webpack config, that one will
331
- have precedence over the one specified here.
309
+ /* List of conditions to check for in the exports field.
310
+ Only works when the 'exportsFields' array is non-empty.
332
311
  */
333
312
  conditionNames: ["import", "require", "node", "default", "types"],
334
313
  /*
335
314
  The extensions, by default are the same as the ones dependency-cruiser
336
315
  can access (run \`npx depcruise --info\` to see which ones that are in
337
- _your_ environment. If that list is larger than what you need (e.g.
338
- it contains .js, .jsx, .ts, .tsx, .cts, .mts - but you don't use
339
- TypeScript you can pass just the extensions you actually use (e.g.
340
- [".js", ".jsx"]). This can speed up the most expensive step in
341
- dependency cruising (module resolution) quite a bit.
316
+ _your_ environment. If that list is larger than you need you can pass
317
+ the extensions you actually use (e.g. [".js", ".jsx"]). This can speed
318
+ up the most expensive step in dependency cruising (module resolution)
319
+ quite a bit.
342
320
  */
343
321
  {{extensionsAttribute}}
344
- /*
345
- If your TypeScript project makes use of types specified in 'types'
346
- fields in package.jsons of external dependencies, specify "types"
347
- in addition to "main" in here, so enhanced-resolve (the resolver
348
- dependency-cruiser uses) knows to also look there. You can also do
349
- this if you're not sure, but still use TypeScript. In a future version
350
- of dependency-cruiser this will likely become the default.
351
- */
322
+ /* What to consider a 'main' field in package.json */
352
323
  {{mainFieldsAttribute}}
353
324
  /*
354
- A list of alias fields in manifests (package.jsons).
355
- Specify a field, such as browser, to be parsed according to
356
- [this specification](https://github.com/defunctzombie/package-browser-field-spec).
357
- Also see [resolve.alias](https://webpack.js.org/configuration/resolve/#resolvealiasfields)
358
- in the webpack docs.
325
+ A list of alias fields in package.jsons
326
+ See [this specification](https://github.com/defunctzombie/package-browser-field-spec) and
327
+ the [resolve.alias](https://webpack.js.org/configuration/resolve/#resolvealiasfields)
328
+ documentation in the webpack docs.
359
329
 
360
330
  Defaults to an empty array (don't use any alias fields).
361
331
  */
@@ -377,70 +347,13 @@ module.exports = {
377
347
  */
378
348
  // theme: {
379
349
  // graph: {
380
- // /* use splines: "ortho" for straight lines. Be aware though
381
- // graphviz might take a long time calculating ortho(gonal)
382
- // routings.
350
+ // /* splines: "ortho" will give you straight lines at the expense of
351
+ // being slow to render on big graphs
352
+ // splines: "true" will give you bezier curves which are faster
353
+ // but might not look as nice
383
354
  // */
384
355
  // splines: "true"
385
356
  // },
386
- // modules: [
387
- // {
388
- // criteria: { matchesFocus: true },
389
- // attributes: {
390
- // fillcolor: "lime",
391
- // penwidth: 2,
392
- // },
393
- // },
394
- // {
395
- // criteria: { matchesFocus: false },
396
- // attributes: {
397
- // fillcolor: "lightgrey",
398
- // },
399
- // },
400
- // {
401
- // criteria: { matchesReaches: true },
402
- // attributes: {
403
- // fillcolor: "lime",
404
- // penwidth: 2,
405
- // },
406
- // },
407
- // {
408
- // criteria: { matchesReaches: false },
409
- // attributes: {
410
- // fillcolor: "lightgrey",
411
- // },
412
- // },
413
- // {
414
- // criteria: { source: "^src/model" },
415
- // attributes: { fillcolor: "#ccccff" }
416
- // },
417
- // {
418
- // criteria: { source: "^src/view" },
419
- // attributes: { fillcolor: "#ccffcc" }
420
- // },
421
- // ],
422
- // dependencies: [
423
- // {
424
- // criteria: { "rules[0].severity": "error" },
425
- // attributes: { fontcolor: "red", color: "red" }
426
- // },
427
- // {
428
- // criteria: { "rules[0].severity": "warn" },
429
- // attributes: { fontcolor: "orange", color: "orange" }
430
- // },
431
- // {
432
- // criteria: { "rules[0].severity": "info" },
433
- // attributes: { fontcolor: "blue", color: "blue" }
434
- // },
435
- // {
436
- // criteria: { resolved: "^src/model" },
437
- // attributes: { color: "#0000ff77" }
438
- // },
439
- // {
440
- // criteria: { resolved: "^src/view" },
441
- // attributes: { color: "#00770077" }
442
- // }
443
- // ]
444
357
  // }
445
358
  },
446
359
  archi: {
@@ -455,7 +368,7 @@ module.exports = {
455
368
  https://github.com/sverweij/dependency-cruiser/blob/main/doc/options-reference.md#reporteroptions
456
369
  for details and some examples. If you don't specify a theme
457
370
  for 'archi' dependency-cruiser will use the one specified in the
458
- dot section (see above), if any, and otherwise use the default one.
371
+ dot section above and otherwise use the default one.
459
372
  */
460
373
  // theme: {
461
374
  // },
@@ -50,7 +50,7 @@ function getOneShotConfig(pOneShotConfigId) {
50
50
  const lOneShotConfigs = new Map([
51
51
  ["yes", lBaseConfig],
52
52
  [
53
- "experimental-scripts",
53
+ "x-scripts",
54
54
  {
55
55
  updateManifest: fileExists(PACKAGE_MANIFEST),
56
56
  ...lBaseConfig,
@@ -1,4 +1,4 @@
1
- export type OneShotConfigIDType = "yes" | "experimental-scripts";
1
+ export type OneShotConfigIDType = "yes" | "x-scripts";
2
2
 
3
3
  export interface IInitConfig {
4
4
  /**
@@ -102,7 +102,7 @@ export function addRunScriptsToManifest(pManifest, pAdditionalRunScripts) {
102
102
  const lManifest = { ...(pManifest || {}) };
103
103
  const lExistingRunScripts = lManifest.scripts || {};
104
104
 
105
- // This could instead simply be done with
105
+ // This could instead be done with
106
106
  // {...pAdditionalScriptEntries, ...lManifest} and no logic at all,
107
107
  // but that'd add the new scripts on top, which doesn't feel right
108
108
  //
@@ -1,53 +1,20 @@
1
1
  import { readFile } from "node:fs/promises";
2
2
  import { fileURLToPath } from "node:url";
3
+ import { getHeader, getFooter } from "#report/dot-webpage/wrap-in-html.mjs";
3
4
 
4
5
  const STYLESHEET_FILE = fileURLToPath(
5
- new URL("svg-in-html-snippets/style.css", import.meta.url),
6
+ new URL(
7
+ "../../report/dot-webpage/svg-in-html-snippets/style.css",
8
+ import.meta.url,
9
+ ),
6
10
  );
7
11
  const SCRIPT_FILE = fileURLToPath(
8
- new URL("svg-in-html-snippets/script.js", import.meta.url),
12
+ new URL(
13
+ "../../report/dot-webpage/svg-in-html-snippets/script.js",
14
+ import.meta.url,
15
+ ),
9
16
  );
10
17
 
11
- /**
12
- * @param {string} pStylesheet
13
- * @returns {string}
14
- */
15
- function getHeader(pStylesheet) {
16
- return `<!doctype html>
17
- <html lang="en" dir="ltr">
18
- <head>
19
- <meta charset="utf-8" />
20
- <title>dependency graph</title>
21
- <style>
22
- ${pStylesheet}
23
- </style>
24
- </head>
25
- <body>
26
- <button id="button_help">?</button>
27
- <div id="hints" class="hint" style="display: none">
28
- <button id="close-hints">x</button>
29
- <span id="hint-text"></span>
30
- <ul>
31
- <li><b>Hover</b> - highlight</li>
32
- <li><b>Right-click</b> - pin highlight</li>
33
- <li><b>ESC</b> - clear</li>
34
- </ul>
35
- </div>
36
- `;
37
- }
38
-
39
- /**
40
- * @param {string} pScript
41
- * @returns {string}
42
- */
43
- function getFooter(pScript) {
44
- return ` <script>
45
- ${pScript}
46
- </script>
47
- </body>
48
- </html>`;
49
- }
50
-
51
18
  /**
52
19
  * Slaps the stuff in the passed stream in between the contents
53
20
  * of the header and the footer and returns it as a string.
@@ -40,13 +40,15 @@ function writeToStdOut(pString, pBufferSize = PIPE_BUFFER_SIZE) {
40
40
  }
41
41
  }
42
42
  export function write(pOutputTo, pContent) {
43
- const lContentWithTrailingNewline = pContent.endsWith("\n")
44
- ? pContent
45
- : `${pContent}\n`;
43
+ // previously we checked here to ensure pContent ended on an EOL (and if it
44
+ // didn't we added one). As for one or two reporters we DON'T want this
45
+ // to happen automatically (e.g. the 'null' one) we've moved that check
46
+ // to the individual reporters and with a unit test ensured that all
47
+ // reporters (current and future) keep doing that.
46
48
  if ("-" === pOutputTo) {
47
- writeToStdOut(lContentWithTrailingNewline);
49
+ writeToStdOut(pContent);
48
50
  } else {
49
- writeToFile(pOutputTo, lContentWithTrailingNewline);
51
+ writeToFile(pOutputTo, pContent);
50
52
  }
51
53
  }
52
54
 
@@ -37,8 +37,9 @@ async function processExtends(pReturnValue, pAlreadyVisited, pBaseDirectory) {
37
37
  * Reads the file with name `pConfigFileName` returns the parsed cruise
38
38
  * options.
39
39
  *
40
- * You can safely ignore the optional parameters. Simply this should work (given
41
- * `.dependency-cruiser.js` exists and contains a valid dependency-cruiser config)
40
+ * You can safely ignore the optional parameters. This should work (given
41
+ * `.dependency-cruiser.js` exists and contains a valid dependency-cruiser
42
+ * config)
42
43
  *
43
44
  * ```javascript
44
45
  * const depcruiseConfig = extractDepcruiseConfig("./.dependency-cruiser.js")
@@ -1,7 +1,7 @@
1
1
  /* eslint-disable max-lines */
2
2
  import { isAbsolute, resolve as path_resolve } from "node:path";
3
3
  import { join as posix_join } from "node:path/posix";
4
- import { isMatch } from "picomatch";
4
+ import picomatch from "picomatch";
5
5
  import getExtension from "#utl/get-extension.mjs";
6
6
 
7
7
  let gFollowableExtensionsCache = new Set();
@@ -158,7 +158,7 @@ function isWorkspaceAliased(pModuleName, pResolvedModuleName, pManifest) {
158
158
  (pWorkspace) =>
159
159
  pWorkspace.endsWith("/") ? `${pWorkspace}**` : `${pWorkspace}/**`,
160
160
  );
161
- if (isMatch(pResolvedModuleName, lModuleFriendlyWorkspaceGlobs)) {
161
+ if (picomatch.isMatch(pResolvedModuleName, lModuleFriendlyWorkspaceGlobs)) {
162
162
  return true;
163
163
  }
164
164
  // it's possible to run node with --preserve-symlinks, in which case
@@ -176,7 +176,10 @@ function isWorkspaceAliased(pModuleName, pResolvedModuleName, pManifest) {
176
176
  lModuleFriendlyWorkspaceGlobs.map(
177
177
  (pWorkspace) => `(node_modules/)?${pWorkspace}`,
178
178
  );
179
- return isMatch(pModuleName, lModuleFriendlyWorkspaceGlobsWithNodeModules);
179
+ return picomatch.isMatch(
180
+ pModuleName,
181
+ lModuleFriendlyWorkspaceGlobsWithNodeModules,
182
+ );
180
183
  }
181
184
  return false;
182
185
  }
package/src/meta.js CHANGED
@@ -1,7 +1,7 @@
1
1
  /* generated - don't edit */
2
2
 
3
3
  module.exports = {
4
- version: "16.1.0",
4
+ version: "16.2.1",
5
5
  engines: {
6
6
  node: "^18.17||>=20",
7
7
  },
@@ -1,6 +1,8 @@
1
1
  import has from "lodash/has.js";
2
2
  import { anonymizePath, WHITELIST_RE } from "./anonymize-path.mjs";
3
3
 
4
+ const EOL = "\n";
5
+
4
6
  function anonymizePathArray(pPathArray, pWordList) {
5
7
  // the coverage ignore is here because the || [] branch isn't taken when running
6
8
  // tests and with the current setup of the anonymize module that's not going
@@ -182,11 +184,15 @@ export default function reportAnonymous(pResults, pAnonymousReporterOptions) {
182
184
  pResults?.summary?.optionsUsed?.reporterOptions?.anon?.wordlist ?? [];
183
185
  }
184
186
  return {
185
- output: JSON.stringify(
186
- anonymize(pResults, sanitizeWordList(lAnonymousReporterOptions.wordlist)),
187
- null,
188
- " ",
189
- ),
187
+ output:
188
+ JSON.stringify(
189
+ anonymize(
190
+ pResults,
191
+ sanitizeWordList(lAnonymousReporterOptions.wordlist),
192
+ ),
193
+ null,
194
+ " ",
195
+ ) + EOL,
190
196
  exitCode: 0,
191
197
  };
192
198
  }
@@ -1,4 +1,5 @@
1
1
  const DEFAULT_JSON_INDENT = 2;
2
+ const EOL = "\n";
2
3
 
3
4
  /**
4
5
  * Sample plugin
@@ -12,11 +13,12 @@ const DEFAULT_JSON_INDENT = 2;
12
13
  */
13
14
  export default function baseline(pCruiseResult) {
14
15
  return {
15
- output: JSON.stringify(
16
- pCruiseResult.summary.violations,
17
- null,
18
- DEFAULT_JSON_INDENT
19
- ),
16
+ output:
17
+ JSON.stringify(
18
+ pCruiseResult.summary.violations,
19
+ null,
20
+ DEFAULT_JSON_INDENT,
21
+ ) + EOL,
20
22
  exitCode: 0,
21
23
  };
22
24
  }
@@ -0,0 +1,77 @@
1
+ // @ts-check
2
+ import { spawnSync } from "node:child_process";
3
+ import dotModuleReporter from "../dot/dot-module.mjs";
4
+ import { wrapInHTML } from "./wrap-in-html.mjs";
5
+
6
+ const CONSTANTS = {
7
+ exec: "dot",
8
+ format: "svg",
9
+ };
10
+
11
+ /**
12
+ * @param {string} pDot Dot program
13
+ * @param {IDotWebpageReporterOptions} pOptions
14
+ * @returns {string} the dot program converted to svg, wrapped in html
15
+ */
16
+ function convert(pDot, pOptions) {
17
+ /* c8 ignore next */
18
+ const lSpawnFunction = pOptions.spawnFunction || spawnSync;
19
+ const { stdout, status, error } = lSpawnFunction(
20
+ CONSTANTS.exec,
21
+ [`-T${CONSTANTS.format}`],
22
+ {
23
+ input: pDot,
24
+ },
25
+ );
26
+ if (status === 0) {
27
+ return stdout.toString("binary");
28
+ } else if (error) {
29
+ throw error;
30
+ } else {
31
+ throw new Error(`GraphViz' dot returned an error (exit code ${status})`);
32
+ }
33
+ }
34
+
35
+ /**
36
+ * @param {IDotWebpageReporterOptions} pOptions
37
+ * @returns {boolean}
38
+ */
39
+ function isAvailable(pOptions) {
40
+ /* c8 ignore next */
41
+ const lSpawnFunction = pOptions.spawnFunction || spawnSync;
42
+ const { status, stderr } = lSpawnFunction(CONSTANTS.exec, ["-V"]);
43
+ return (
44
+ status === 0 && stderr.toString("utf8").startsWith("dot - graphviz version")
45
+ );
46
+ }
47
+
48
+ /**
49
+ * @typedef {import("../../../types/cruise-result.mjs").ICruiseResult} ICruiseResult
50
+ * @typedef {import("../../../types/reporter-options.mjs").IDotReporterOptions} IDotReporterOptions
51
+ * @typedef {import("../../../types/dependency-cruiser.mjs").IReporterOutput} IReporterOutput
52
+ * @typedef {IDotReporterOptions & { spawnFunction?: typeof spawnSync }} IDotWebpageReporterOptions
53
+ */
54
+
55
+ /**
56
+ * Returns the results of a cruise as an svg picture, using the dot reporter
57
+ * and a system call to the GraphViz dot executable.
58
+ *
59
+ * @param {ICruiseResult} pResults
60
+ * @param {IDotWebpageReporterOptions} pDotWebpageReporterOptions
61
+ * @returns {IReporterOutput}
62
+ */
63
+ export default function dotWebpage(pResults, pDotWebpageReporterOptions) {
64
+ const { output } = dotModuleReporter(pResults, pDotWebpageReporterOptions);
65
+
66
+ if (!isAvailable(pDotWebpageReporterOptions)) {
67
+ throw new Error(
68
+ "GraphViz dot, which is required for the 'x-dot-webpage' reporter doesn't " +
69
+ "seem to be available on this system. See the GraphViz download page for " +
70
+ "instruction on how to get it on your system: https://www.graphviz.org/download/",
71
+ );
72
+ }
73
+ return {
74
+ output: wrapInHTML(convert(output, pDotWebpageReporterOptions)),
75
+ exitCode: 0,
76
+ };
77
+ }
@@ -0,0 +1,62 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { fileURLToPath } from "node:url";
3
+
4
+ const STYLESHEET_FILE = fileURLToPath(
5
+ new URL("svg-in-html-snippets/style.css", import.meta.url),
6
+ );
7
+ const SCRIPT_FILE = fileURLToPath(
8
+ new URL("svg-in-html-snippets/script.js", import.meta.url),
9
+ );
10
+
11
+ /**
12
+ * @param {string} pStylesheet
13
+ * @returns {string}
14
+ */
15
+ export function getHeader(pStylesheet) {
16
+ return `<!doctype html>
17
+ <html lang="en" dir="ltr">
18
+ <head>
19
+ <meta charset="utf-8" />
20
+ <title>dependency graph</title>
21
+ <style>
22
+ ${pStylesheet}
23
+ </style>
24
+ </head>
25
+ <body>
26
+ <button id="button_help">?</button>
27
+ <div id="hints" class="hint" style="display: none">
28
+ <button id="close-hints">x</button>
29
+ <span id="hint-text"></span>
30
+ <ul>
31
+ <li><b>Hover</b> - highlight</li>
32
+ <li><b>Right-click</b> - pin highlight</li>
33
+ <li><b>ESC</b> - clear</li>
34
+ </ul>
35
+ </div>
36
+ `;
37
+ }
38
+
39
+ /**
40
+ * @param {string} pScript
41
+ * @returns {string}
42
+ */
43
+ export function getFooter(pScript) {
44
+ return ` <script>
45
+ ${pScript}
46
+ </script>
47
+ </body>
48
+ </html>
49
+ `;
50
+ }
51
+
52
+ /**
53
+ *
54
+ * @param {string} pSVG
55
+ * @returns {string}
56
+ */
57
+ export function wrapInHTML(pSVG) {
58
+ const lStylesheet = readFileSync(STYLESHEET_FILE, "utf8");
59
+ const lScript = readFileSync(SCRIPT_FILE, "utf8");
60
+
61
+ return getHeader(lStylesheet) + pSVG + getFooter(lScript);
62
+ }
@@ -209,4 +209,5 @@ export default `<!DOCTYPE html>
209
209
  {{runDate}}</p>
210
210
  </footer>
211
211
  </body>
212
- </html>`;
212
+ </html>
213
+ `;
@@ -181,4 +181,5 @@ export default `<!DOCTYPE html>
181
181
  <body>
182
182
  {{table-here}}
183
183
  </body>
184
- </html>`;
184
+ </html>
185
+ `;
@@ -23,6 +23,7 @@ const TYPE2MODULE = new Map([
23
23
  ["null", "./null.mjs"],
24
24
  ["teamcity", "./teamcity.mjs"],
25
25
  ["text", "./text.mjs"],
26
+ ["x-dot-webpage", "./dot-webpage/dot-module.mjs"],
26
27
  ]);
27
28
 
28
29
  /**
@@ -1,3 +1,4 @@
1
+ const EOL = "\n";
1
2
  /**
2
3
  * Returns the results of a cruise in JSON
3
4
  *
@@ -6,7 +7,7 @@
6
7
  */
7
8
  export default function json(pResults) {
8
9
  return {
9
- output: JSON.stringify(pResults, null, " "),
10
+ output: JSON.stringify(pResults, null, " ") + EOL,
10
11
  exitCode: 0,
11
12
  };
12
13
  }
@@ -7,6 +7,7 @@ const SEVERITY2TEAMCITY_SEVERITY = new Map([
7
7
  ["warn", "WARNING"],
8
8
  ["info", "INFO"],
9
9
  ]);
10
+ const EOL = "\n";
10
11
 
11
12
  function severity2teamcitySeverity(pSeverity) {
12
13
  return SEVERITY2TEAMCITY_SEVERITY.get(pSeverity) || "INFO";
@@ -159,9 +160,10 @@ export default function teamcity(pResults) {
159
160
  const lIgnoredCount = pResults?.summary?.ignore ?? 0;
160
161
 
161
162
  return {
162
- output: reportViolatedRules(lRuleSet, lViolations, lIgnoredCount)
163
- .concat(reportViolations(lViolations, lIgnoredCount))
164
- .reduce((pAll, pCurrent) => `${pAll}${pCurrent}\n`, ""),
163
+ output:
164
+ reportViolatedRules(lRuleSet, lViolations, lIgnoredCount)
165
+ .concat(reportViolations(lViolations, lIgnoredCount))
166
+ .reduce((pAll, pCurrent) => `${pAll}${pCurrent}\n`, "") || EOL,
165
167
  exitCode: pResults.summary.error,
166
168
  };
167
169
  }
@@ -1,10 +1,10 @@
1
- import { EOL } from "node:os";
2
1
  import figures from "figures";
3
2
  import chalk from "chalk";
4
3
 
5
4
  const DEFAULT_OPTIONS = {
6
5
  highlightFocused: false,
7
6
  };
7
+ const EOL = "\n";
8
8
 
9
9
  /**
10
10
  *
@@ -30,9 +30,9 @@ function toFlatDependencies(pModules, pModulesInFocus, pHighlightFocused) {
30
30
  return pModules.reduce(
31
31
  (pAll, pModule) =>
32
32
  pAll.concat(
33
- toFlatModuleDependencies(pModule, pModulesInFocus, pHighlightFocused)
33
+ toFlatModuleDependencies(pModule, pModulesInFocus, pHighlightFocused),
34
34
  ),
35
- []
35
+ [],
36
36
  );
37
37
  }
38
38
 
@@ -55,7 +55,7 @@ function getModulesInFocus(pModules) {
55
55
  return new Set(
56
56
  pModules
57
57
  .filter((pModule) => pModule.matchesFocus || pModule.matchesReaches)
58
- .map((pModule) => pModule.source)
58
+ .map((pModule) => pModule.source),
59
59
  );
60
60
  }
61
61
 
@@ -68,13 +68,15 @@ function getModulesInFocus(pModules) {
68
68
  function report(pResults, pOptions) {
69
69
  const lOptions = { ...DEFAULT_OPTIONS, ...pOptions };
70
70
 
71
- return toFlatDependencies(
72
- pResults.modules,
73
- getModulesInFocus(pResults.modules),
74
- lOptions.highlightFocused === true
75
- ).reduce(
76
- (pAll, pDependency) => pAll.concat(stringify(pDependency)).concat(EOL),
77
- ""
71
+ return (
72
+ toFlatDependencies(
73
+ pResults.modules,
74
+ getModulesInFocus(pResults.modules),
75
+ lOptions.highlightFocused === true,
76
+ ).reduce(
77
+ (pAll, pDependency) => pAll.concat(stringify(pDependency)).concat(EOL),
78
+ "",
79
+ ) || EOL
78
80
  );
79
81
  }
80
82
 
@@ -4,8 +4,9 @@ import type { ICruiseOptions } from "../options.mjs";
4
4
  * Reads the file with name `pConfigFileName` returns the parsed cruise
5
5
  * options.
6
6
  *
7
- * You can safely ignore the optional parameters. Simply this should work (given
8
- * `.dependency-cruiser.js` exists and contains a valid dependency-cruiser config)
7
+ * You can safely ignore the optional parameters. This should work (given
8
+ * `.dependency-cruiser.js` exists and contains a valid dependency-cruiser
9
+ * config)
9
10
  *
10
11
  * ```javascript
11
12
  * const depcruiseConfig = extractDepcruiseConfig("./.dependency-cruiser.js")
@@ -173,7 +173,6 @@ export interface IDependentsModuleRestrictionType extends IBaseRestrictionType {
173
173
  * Matches when the number of times the 'to' module is used falls below (<)
174
174
  * this number. Caveat: only works in concert with path and pathNot restrictions
175
175
  * in the from and to parts of the rule; other conditions will be ignored.
176
- * (somewhat experimental; - syntax can change over time without a major bump)
177
176
  * E.g. to flag modules that are used only once or not at all, use 2 here.
178
177
  */
179
178
  numberOfDependentsLessThan?: number;
@@ -181,7 +180,6 @@ export interface IDependentsModuleRestrictionType extends IBaseRestrictionType {
181
180
  * Matches when the number of times the 'to' module is used rises above (<)
182
181
  * this number. Caveat: only works in concert with path and pathNot restrictions
183
182
  * in the from and to parts of the rule; other conditions will be ignored.
184
- * (somewhat experimental; - syntax can change over time without a major bump)
185
183
  * E.g. to flag modules that are used more than 10 times, use 10 here.
186
184
  */
187
185
  numberOfDependentsMoreThan?: number;
@@ -43,13 +43,10 @@ export interface IRegularForbiddenRuleType extends IBaseRuleType {
43
43
  /**
44
44
  * What to apply the rule to - modules (the default) or folders. Switching
45
45
  * the scope to 'folder' can be useful in rules where this makes a difference
46
- * like those regarding circular dependencies or instability. Two things
47
- * to note when you decide to use 'folder' level scope: (1) the 'scope' attribute
48
- * is experimental - the way to indicate the scope of a rule can change
49
- * over time without dependency-cruiser undergoing a major bump. (2) Only
50
- * the to.moreUnstable, to.circular, and path (both from and to) attributes
51
- * work at the moment. Other attributes will follow suit in releases
52
- * after 11.6.0.
46
+ * like those regarding circular dependencies or instability.
47
+ * Only the to.moreUnstable, to.circular, and path (both from and to)
48
+ * attributes work at the moment. Other attributes will follow suit in later
49
+ * releases (depending on demand).
53
50
  */
54
51
  scope?: RuleScopeType;
55
52
  }