dependency-cruiser 15.4.0 → 15.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/configs/plugins/3d-reporter-plugin.mjs +127 -0
- package/configs/plugins/stats-reporter-plugin.mjs +82 -0
- package/package.json +33 -23
- package/src/cache/metadata-strategy.mjs +1 -1
- package/src/cli/init-config/config-template.mjs +9 -1
- package/src/cli/tools/svg-in-html-snippets/{script.snippet.js → script.js} +47 -12
- package/src/cli/tools/svg-in-html-snippets/style.css +101 -0
- package/src/cli/tools/wrap-stream-in-html.mjs +57 -15
- package/src/config-utl/extract-depcruise-config/index.mjs +1 -1
- package/src/config-utl/extract-webpack-resolve-config.mjs +14 -11
- package/src/enrich/add-validations.mjs +3 -3
- package/src/enrich/soften-known-violations.mjs +4 -4
- package/src/enrich/summarize/index.mjs +3 -3
- package/src/extract/gather-initial-sources.mjs +2 -2
- package/src/extract/get-dependencies.mjs +2 -2
- package/src/extract/resolve/determine-dependency-types.mjs +3 -3
- package/src/extract/resolve/index.mjs +3 -3
- package/src/extract/transpile/meta.d.ts +1 -1
- package/src/graph-utl/filter-bank.mjs +2 -2
- package/src/main/index.d.ts +1 -1
- package/src/main/options/normalize.mjs +1 -1
- package/src/main/rule-set/normalize.mjs +1 -1
- package/src/meta.js +1 -1
- package/src/report/anon/index.mjs +7 -7
- package/src/report/azure-devops.mjs +11 -11
- package/src/report/csv.mjs +3 -2
- package/src/report/dot/default-theme.mjs +1 -1
- package/src/report/error-html/index.mjs +6 -6
- package/src/report/error.mjs +1 -1
- package/src/report/html/index.mjs +1 -1
- package/src/report/markdown.mjs +5 -5
- package/src/validate/index.d.ts +4 -4
- package/src/validate/match-folder-dependency-rule.mjs +2 -2
- package/src/validate/match-module-rule.mjs +12 -12
- package/src/validate/violates-required-rule.mjs +2 -2
- package/types/baseline-violations.d.mts +3 -0
- package/types/{extract-babel-config.d.ts → config-utl/extract-babel-config.d.mts} +1 -1
- package/types/{extract-depcruise-config.d.ts → config-utl/extract-depcruise-config.d.mts} +2 -2
- package/types/{extract-ts-config.d.ts → config-utl/extract-ts-config.d.mts} +1 -1
- package/types/{extract-webpack-resolve-config.d.ts → config-utl/extract-webpack-resolve-config.d.mts} +4 -3
- package/types/{configuration.d.ts → configuration.d.mts} +2 -2
- package/types/{cruise-result.d.ts → cruise-result.d.mts} +12 -8
- package/types/{dependency-cruiser.d.ts → dependency-cruiser.d.mts} +9 -9
- package/types/{filter-types.d.ts → filter-types.d.mts} +1 -1
- package/types/{options.d.ts → options.d.mts} +7 -7
- package/types/plugins/3d-reporter-plugin.d.mts +13 -0
- package/types/plugins/mermaid-reporter-plugin.d.mts +14 -0
- package/types/plugins/stats-reporter-plugin.d.mts +13 -0
- package/types/{reporter-options.d.ts → reporter-options.d.mts} +2 -2
- package/types/{resolve-options.d.ts → resolve-options.d.mts} +1 -1
- package/types/{restrictions.d.ts → restrictions.d.mts} +1 -1
- package/types/{rule-set.d.ts → rule-set.d.mts} +3 -3
- package/types/{rule-summary.d.ts → rule-summary.d.mts} +1 -1
- package/types/{strict-filter-types.d.ts → strict-filter-types.d.mts} +3 -3
- package/types/{strict-options.d.ts → strict-options.d.mts} +7 -7
- package/types/{strict-restrictions.d.ts → strict-restrictions.d.mts} +2 -2
- package/types/{strict-rule-set.d.ts → strict-rule-set.d.mts} +5 -5
- package/types/{violations.d.ts → violations.d.mts} +2 -2
- package/src/cli/tools/svg-in-html-snippets/footer.snippet.html +0 -2
- package/src/cli/tools/svg-in-html-snippets/header.snippet.html +0 -108
- package/types/baseline-violations.d.ts +0 -3
- /package/types/{cache-options.d.ts → cache-options.d.mts} +0 -0
- /package/types/{shared-types.d.ts → shared-types.d.mts} +0 -0
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import * as path from "node:path";
|
|
2
|
+
import figures from "figures";
|
|
3
|
+
|
|
4
|
+
const TEMPLATE = `
|
|
5
|
+
<html>
|
|
6
|
+
<head>
|
|
7
|
+
<style> body { margin: 0; } </style>
|
|
8
|
+
<script type="text/javascript" src="https://unpkg.com/three"></script>
|
|
9
|
+
<script type="text/javascript" src="https://unpkg.com/three-spritetext"></script>
|
|
10
|
+
<script type="text/javascript" src="https://unpkg.com/3d-force-graph"></script>
|
|
11
|
+
</head>
|
|
12
|
+
|
|
13
|
+
<body>
|
|
14
|
+
<div id="3d-graph"></div>
|
|
15
|
+
|
|
16
|
+
<script>
|
|
17
|
+
const gData = {
|
|
18
|
+
nodes: @@NODES@@,
|
|
19
|
+
links: @@LINKS@@
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const elem = document.getElementById('3d-graph')
|
|
23
|
+
const Graph = ForceGraph3D()
|
|
24
|
+
(elem)
|
|
25
|
+
.graphData(gData)
|
|
26
|
+
.nodeAutoColorBy('group')
|
|
27
|
+
.nodeLabel(node => node.label)
|
|
28
|
+
.onNodeHover(node => elem.style.cursor = node ? 'pointer' : null)
|
|
29
|
+
.onNodeClick(node => {
|
|
30
|
+
// Aim at node from outside it
|
|
31
|
+
const distance = 40;
|
|
32
|
+
const distRatio = 1 + distance/Math.hypot(node.x, node.y, node.z);
|
|
33
|
+
|
|
34
|
+
Graph.cameraPosition(
|
|
35
|
+
{ x: node.x * distRatio, y: node.y * distRatio, z: node.z * distRatio }, // new position
|
|
36
|
+
node, // lookAt ({ x, y, z })
|
|
37
|
+
3000 // ms transition duration
|
|
38
|
+
)
|
|
39
|
+
})
|
|
40
|
+
/* nice idea, but the 3D graph tends to look cluttered. Also GPU/ CPU
|
|
41
|
+
intensive when run on a serious code base (e.g. react that has
|
|
42
|
+
~4500 nodes and ~10000 links
|
|
43
|
+
*/
|
|
44
|
+
.nodeThreeObject(node => {
|
|
45
|
+
const sprite = new SpriteText(node.displayname);
|
|
46
|
+
sprite.material.depthWrite = true; // make sprite background transparent
|
|
47
|
+
sprite.color = node.color;
|
|
48
|
+
sprite.textHeight = 6;
|
|
49
|
+
return sprite;
|
|
50
|
+
})
|
|
51
|
+
.linkOpacity(0.2)
|
|
52
|
+
.linkWidth(2)
|
|
53
|
+
.linkDirectionalArrowLength(4)
|
|
54
|
+
.linkDirectionalParticles(7) // cool but a bit GPU intensive
|
|
55
|
+
.linkLabel(link => link.label)
|
|
56
|
+
.onLinkHover(link => elem.style.cursor = link ? 'pointer' : null)
|
|
57
|
+
|
|
58
|
+
</script>
|
|
59
|
+
</body>
|
|
60
|
+
</html>`;
|
|
61
|
+
|
|
62
|
+
function deriveGroup(pFileName) {
|
|
63
|
+
let lReturnValue = "unknown";
|
|
64
|
+
const lGroupPositionInRe = 2;
|
|
65
|
+
const lMatch = path.dirname(pFileName).match(/^([^/]+)\/([^/]+)/);
|
|
66
|
+
if (lMatch) {
|
|
67
|
+
// eslint-disable-next-line security/detect-object-injection
|
|
68
|
+
lReturnValue = lMatch[lGroupPositionInRe];
|
|
69
|
+
}
|
|
70
|
+
return lReturnValue;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function formatFileName(pFileName) {
|
|
74
|
+
return `${path.dirname(pFileName)}/<b>${path.basename(pFileName)}</b>`;
|
|
75
|
+
}
|
|
76
|
+
function formatDependency(pFrom, pTo) {
|
|
77
|
+
return `${formatFileName(pFrom)} ${figures.arrowRight}</br>${formatFileName(
|
|
78
|
+
pTo
|
|
79
|
+
)}`;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
*
|
|
84
|
+
* @param {import('../../types/dependency-cruiser').ICruiseResult} pCruiseResult
|
|
85
|
+
*/
|
|
86
|
+
function render3DThing(pCruiseResult) {
|
|
87
|
+
const lNodes = pCruiseResult.modules.map((pModule) => {
|
|
88
|
+
return {
|
|
89
|
+
id: pModule.source,
|
|
90
|
+
displayname: path.basename(pModule.source),
|
|
91
|
+
dirname: path.dirname(pModule.source),
|
|
92
|
+
label: formatFileName(pModule.source),
|
|
93
|
+
group: deriveGroup(pModule.source),
|
|
94
|
+
};
|
|
95
|
+
});
|
|
96
|
+
const lLinks = pCruiseResult.modules.reduce(
|
|
97
|
+
(pAll, pCurrentModule) =>
|
|
98
|
+
pAll.concat(
|
|
99
|
+
pCurrentModule.dependencies.map((pDependency) => ({
|
|
100
|
+
source: pCurrentModule.source,
|
|
101
|
+
target: pDependency.resolved,
|
|
102
|
+
label: formatDependency(pCurrentModule.source, pDependency.resolved),
|
|
103
|
+
}))
|
|
104
|
+
),
|
|
105
|
+
[]
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
return TEMPLATE.replace(/@@NODES@@/g, JSON.stringify(lNodes)).replace(
|
|
109
|
+
/@@LINKS@@/g,
|
|
110
|
+
JSON.stringify(lLinks)
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Sample plugin: 3d representation
|
|
116
|
+
*
|
|
117
|
+
* @param {import('../../types/dependency-cruiser').ICruiseResult} pCruiseResult -
|
|
118
|
+
* the output of a dependency-cruise adhering to dependency-cruiser's
|
|
119
|
+
* cruise result schema
|
|
120
|
+
* @return {import('../../types/dependency-cruiser').IReporterOutput} -
|
|
121
|
+
* output: a string
|
|
122
|
+
* exitCode: 0
|
|
123
|
+
*/
|
|
124
|
+
export default (pCruiseResult) => ({
|
|
125
|
+
output: render3DThing(pCruiseResult),
|
|
126
|
+
exitCode: 0,
|
|
127
|
+
});
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
const MEDIAN = 0.5;
|
|
2
|
+
const P75 = 0.75;
|
|
3
|
+
const DEFAULT_JSON_INDENT = 2;
|
|
4
|
+
|
|
5
|
+
function doMagic(pCruiseResult) {
|
|
6
|
+
let lReturnValue = {};
|
|
7
|
+
|
|
8
|
+
if (pCruiseResult.modules.some((pModule) => pModule.dependents)) {
|
|
9
|
+
const lDependentCounts = pCruiseResult.modules
|
|
10
|
+
.map((pModule) => pModule.dependents.length)
|
|
11
|
+
.sort();
|
|
12
|
+
|
|
13
|
+
lReturnValue = {
|
|
14
|
+
minDependentsPerModule: lDependentCounts[0] || 0,
|
|
15
|
+
maxDependentsPerModule:
|
|
16
|
+
lDependentCounts[Math.max(lDependentCounts.length - 1, 0)] || 0,
|
|
17
|
+
meanDependentsPerModule:
|
|
18
|
+
lDependentCounts.reduce((pAll, pCurrent) => pAll + pCurrent, 0) /
|
|
19
|
+
pCruiseResult.summary.totalCruised,
|
|
20
|
+
medianDependentsPerModule:
|
|
21
|
+
lDependentCounts[
|
|
22
|
+
Math.max(0, Math.floor(lDependentCounts.length * MEDIAN))
|
|
23
|
+
],
|
|
24
|
+
p75DependentsPerModule:
|
|
25
|
+
lDependentCounts[
|
|
26
|
+
Math.max(0, Math.floor(lDependentCounts.length * P75))
|
|
27
|
+
],
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
return lReturnValue;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* returns an object with some stats from the ICruiseResult pCruiseResult it
|
|
34
|
+
* got passed
|
|
35
|
+
*
|
|
36
|
+
* @param {import('../../types/dependency-cruiser').ICruiseResult} pCruiseResult - a result from a cruise.
|
|
37
|
+
* @return {string} an object with some stats
|
|
38
|
+
*/
|
|
39
|
+
function samplePluginReporter(pCruiseResult) {
|
|
40
|
+
const lDependencyCounts = pCruiseResult.modules
|
|
41
|
+
.map((pModule) => pModule.dependencies.length)
|
|
42
|
+
.sort();
|
|
43
|
+
|
|
44
|
+
return {
|
|
45
|
+
moduleCount: pCruiseResult.summary.totalCruised,
|
|
46
|
+
dependencyCount: pCruiseResult.summary.totalDependenciesCruised,
|
|
47
|
+
minDependenciesPerModule: lDependencyCounts[0] || 0,
|
|
48
|
+
maxDependenciesPerModule:
|
|
49
|
+
lDependencyCounts[Math.max(lDependencyCounts.length - 1, 0)] || 0,
|
|
50
|
+
meanDependenciesPerModule:
|
|
51
|
+
pCruiseResult.summary.totalDependenciesCruised /
|
|
52
|
+
pCruiseResult.summary.totalCruised,
|
|
53
|
+
medianDependenciesPerModule:
|
|
54
|
+
lDependencyCounts[
|
|
55
|
+
Math.max(0, Math.floor(lDependencyCounts.length * MEDIAN))
|
|
56
|
+
],
|
|
57
|
+
p75DependenciesPerModule:
|
|
58
|
+
lDependencyCounts[
|
|
59
|
+
Math.max(0, Math.floor(lDependencyCounts.length * P75))
|
|
60
|
+
],
|
|
61
|
+
...doMagic(pCruiseResult),
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Sample plugin
|
|
67
|
+
*
|
|
68
|
+
* @param {import('../../types/dependency-cruiser').ICruiseResult} pCruiseResult -
|
|
69
|
+
* the output of a dependency-cruise adhering to dependency-cruiser's
|
|
70
|
+
* cruise result schema
|
|
71
|
+
* @return {import('../../types/dependency-cruiser').IReporterOutput} -
|
|
72
|
+
* output: some stats on modules and dependencies in json format
|
|
73
|
+
* exitCode: 0
|
|
74
|
+
*/
|
|
75
|
+
export default (pCruiseResult) => ({
|
|
76
|
+
output: JSON.stringify(
|
|
77
|
+
samplePluginReporter(pCruiseResult),
|
|
78
|
+
null,
|
|
79
|
+
DEFAULT_JSON_INDENT
|
|
80
|
+
),
|
|
81
|
+
exitCode: 0,
|
|
82
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dependency-cruiser",
|
|
3
|
-
"version": "15.
|
|
3
|
+
"version": "15.5.0",
|
|
4
4
|
"description": "Validate and visualize dependencies. With your rules. JavaScript, TypeScript, CoffeeScript. ES6, CommonJS, AMD.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"static analysis",
|
|
@@ -51,39 +51,49 @@
|
|
|
51
51
|
},
|
|
52
52
|
"exports": {
|
|
53
53
|
".": {
|
|
54
|
-
"
|
|
55
|
-
"
|
|
54
|
+
"types": "./types/dependency-cruiser.d.mts",
|
|
55
|
+
"import": "./src/main/index.mjs"
|
|
56
56
|
},
|
|
57
57
|
"./config-utl/extract-babel-config": {
|
|
58
|
-
"
|
|
59
|
-
"
|
|
58
|
+
"types": "./types/config-utl/extract-babel-config.d.mts",
|
|
59
|
+
"import": "./src/config-utl/extract-babel-config.mjs"
|
|
60
60
|
},
|
|
61
61
|
"./config-utl/extract-depcruise-config": {
|
|
62
|
-
"
|
|
63
|
-
"
|
|
62
|
+
"types": "./types/config-utl/extract-depcruise-config.d.mts",
|
|
63
|
+
"import": "./src/config-utl/extract-depcruise-config/index.mjs"
|
|
64
64
|
},
|
|
65
65
|
"./config-utl/extract-ts-config": {
|
|
66
|
-
"
|
|
67
|
-
"
|
|
66
|
+
"types": "./types/config-utl/extract-ts-config.d.mts",
|
|
67
|
+
"import": "./src/config-utl/extract-ts-config.mjs"
|
|
68
68
|
},
|
|
69
69
|
"./config-utl/extract-webpack-resolve-config": {
|
|
70
|
-
"
|
|
71
|
-
"
|
|
70
|
+
"types": "./types/config-utl/extract-webpack-resolve-config.d.mts",
|
|
71
|
+
"import": "./src/config-utl/extract-webpack-resolve-config.mjs"
|
|
72
72
|
},
|
|
73
|
-
"./sample-reporter-plugin":
|
|
74
|
-
|
|
75
|
-
|
|
73
|
+
"./sample-reporter-plugin": {
|
|
74
|
+
"types": "./types/plugins/stats-reporter-plugin.d.mts",
|
|
75
|
+
"import": "./configs/plugins/stats-reporter-plugin.mjs"
|
|
76
|
+
},
|
|
77
|
+
"./sample-3d-reporter-plugin": {
|
|
78
|
+
"types": "./types/plugins/3d-reporter-plugin.d.mts",
|
|
79
|
+
"import": "./configs/plugins/3d-reporter-plugin.mjs"
|
|
80
|
+
},
|
|
81
|
+
"./mermaid-reporter-plugin": {
|
|
82
|
+
"types": "./types/plugins/mermaid-reporter-plugin.d.mts",
|
|
83
|
+
"import": "./src/report/mermaid.mjs"
|
|
84
|
+
}
|
|
76
85
|
},
|
|
77
|
-
"types": "types/dependency-cruiser.d.
|
|
86
|
+
"types": "types/dependency-cruiser.d.mts",
|
|
78
87
|
"files": [
|
|
79
88
|
"bin",
|
|
80
89
|
"configs/**/*.js",
|
|
90
|
+
"configs/plugins/",
|
|
81
91
|
"src",
|
|
82
92
|
"!src/**/*.json",
|
|
83
93
|
"!src/**/*.hbs",
|
|
84
94
|
"!src/**/*.md",
|
|
85
95
|
"!**/*.DS_Store",
|
|
86
|
-
"types
|
|
96
|
+
"types/**/*.d.mts",
|
|
87
97
|
"LICENSE",
|
|
88
98
|
"package.json",
|
|
89
99
|
"README.md"
|
|
@@ -103,7 +113,7 @@
|
|
|
103
113
|
"depcruise:graph:doc:fmt-archi": "./bin/depcruise-fmt.mjs -T archi -f - node_modules/.cache/tmp_graph_deps.json | dot -T svg -Gordering=in -Grankdir=TD | tee doc/real-world-samples/dependency-cruiser-archi-graph.svg | node bin/wrap-stream-in-html.mjs > docs/dependency-cruiser-archi-graph.html",
|
|
104
114
|
"depcruise:graph:doc:fmt-dir": "./bin/depcruise-fmt.mjs -T ddot -f - node_modules/.cache/tmp_graph_deps.json | dot -T svg -Grankdir=TD | tee doc/real-world-samples/dependency-cruiser-dir-graph.svg | node bin/wrap-stream-in-html.mjs > docs/dependency-cruiser-dir-graph.html",
|
|
105
115
|
"depcruise:graph:doc:fmt-schema": "cd tools/schema && node ../../bin/dependency-cruise.mjs . --output-type dot | dot -T svg | tee ../overview.svg | node ../../bin/wrap-stream-in-html.mjs > ../../docs/schema-overview.html && cd -",
|
|
106
|
-
"depcruise:graph:doc:fmt-types": "cd types && node ../bin/dependency-cruise.mjs . --output-type dot | dot -T svg
|
|
116
|
+
"depcruise:graph:doc:fmt-types": "cd types && node ../bin/dependency-cruise.mjs . --output-type dot | dot -T svg | tee overview.svg | ../bin/wrap-stream-in-html.mjs > overview.html && cd -",
|
|
107
117
|
"depcruise:graph:doc:samples": "sh tools/generate-samples.sh",
|
|
108
118
|
"depcruise:graph:mermaid": "node ./bin/dependency-cruise.mjs bin src --include-only ^src/ --collapse 2 --output-type mermaid",
|
|
109
119
|
"depcruise:graph:mermaid:diff": "node ./bin/dependency-cruise.mjs bin src test types tools --config configs/.dependency-cruiser-unlimited.mjs --output-type mermaid --reaches \"$(watskeburt $SHA)\"",
|
|
@@ -113,8 +123,8 @@
|
|
|
113
123
|
"depcruise:report:view": "node ./bin/dependency-cruise.mjs src bin test configs types --output-type err-html --config configs/.dependency-cruiser-show-metrics-config.mjs --output-to - | browser",
|
|
114
124
|
"depcruise:focus": "node ./bin/dependency-cruise.mjs src bin test configs types tools --progress --no-cache --output-type text --focus",
|
|
115
125
|
"depcruise:reaches": "node ./bin/dependency-cruise.mjs src bin test configs types tools --progress --no-cache --config configs/.dependency-cruiser-unlimited.mjs --output-type text --reaches",
|
|
116
|
-
"format": "prettier --log-level warn --write \"src/**/*.js\" \"configs/**/*.js\" \"tools/**/*.mjs\" \"bin/*\" \"types/*.d.
|
|
117
|
-
"format:check": "prettier --log-level warn --check \"src/**/*.js\" \"configs/**/*.js\" \"tools/**/*.mjs\" \"bin/*\" \"types/*.d.
|
|
126
|
+
"format": "prettier --log-level warn --write \"src/**/*.js\" \"configs/**/*.js\" \"tools/**/*.mjs\" \"bin/*\" \"types/*.d.mts\" \"test/**/*.spec.{cjs,js}\" \"test/**/*.{spec,utl}.mjs\"",
|
|
127
|
+
"format:check": "prettier --log-level warn --check \"src/**/*.js\" \"configs/**/*.js\" \"tools/**/*.mjs\" \"bin/*\" \"types/*.d.mts\" \"test/**/*.spec.{cjs,js}\" \"test/**/*.{spec,utl}.mjs\"",
|
|
118
128
|
"lint": "npm-run-all --parallel --aggregate-output lint:eslint format:check lint:types",
|
|
119
129
|
"lint:eslint": "eslint bin/dependency-cruise.mjs bin src test configs tools/**/*.mjs --cache --cache-location node_modules/.cache/eslint/",
|
|
120
130
|
"lint:eslint:fix": "eslint --fix bin src test configs tools/**/*.mjs --cache --cache-location node_modules/.cache/eslint/",
|
|
@@ -122,8 +132,8 @@
|
|
|
122
132
|
"lint:fix": "npm-run-all lint:eslint:fix format lint:types:fix",
|
|
123
133
|
"lint:types": "npm-run-all lint:types:tsc lint:types:lint",
|
|
124
134
|
"lint:types:tsc": "tsc --project types/tsconfig.json",
|
|
125
|
-
"lint:types:lint": "eslint --no-ignore --config types/.eslintrc.json types/*.d.
|
|
126
|
-
"lint:types:fix": "eslint --no-ignore --config types/.eslintrc.json --fix types/*.d.
|
|
135
|
+
"lint:types:lint": "eslint --no-ignore --config types/.eslintrc.json types/*.d.mts",
|
|
136
|
+
"lint:types:fix": "eslint --no-ignore --config types/.eslintrc.json --fix types/*.d.mts",
|
|
127
137
|
"prepare": "husky install",
|
|
128
138
|
"scm:push": "run-p --aggregate-output scm:push:*",
|
|
129
139
|
"scm:push:bitbucket-mirror": "run-p --aggregate-output scm:push:bitbucket-mirror:*",
|
|
@@ -186,11 +196,11 @@
|
|
|
186
196
|
"@babel/preset-typescript": "7.23.3",
|
|
187
197
|
"@swc/core": "1.3.99",
|
|
188
198
|
"@types/lodash": "4.14.202",
|
|
189
|
-
"@types/node": "20.
|
|
199
|
+
"@types/node": "20.10.0",
|
|
190
200
|
"@types/prompts": "2.4.9",
|
|
191
201
|
"@typescript-eslint/eslint-plugin": "6.12.0",
|
|
192
202
|
"@typescript-eslint/parser": "6.12.0",
|
|
193
|
-
"@vue/compiler-sfc": "3.3.
|
|
203
|
+
"@vue/compiler-sfc": "3.3.9",
|
|
194
204
|
"c8": "8.0.1",
|
|
195
205
|
"coffeescript": "2.7.0",
|
|
196
206
|
"eslint": "8.54.0",
|
|
@@ -15,7 +15,7 @@ import { bus } from "#utl/bus.mjs";
|
|
|
15
15
|
export default class MetaDataStrategy {
|
|
16
16
|
/**
|
|
17
17
|
* @param {string} _pDirectory
|
|
18
|
-
* @param {import("../../types/cruise-result.
|
|
18
|
+
* @param {import("../../types/cruise-result.mjs").ICruiseResult} _pCachedCruiseResult
|
|
19
19
|
* @param {Object} pOptions
|
|
20
20
|
* @param {Set<string>} pOptions.extensions
|
|
21
21
|
* @param {Set<import("watskeburt").changeTypeType>=} pOptions.interestingChangeTypes
|
|
@@ -154,7 +154,15 @@ module.exports = {
|
|
|
154
154
|
},
|
|
155
155
|
to: {
|
|
156
156
|
dependencyTypes: [
|
|
157
|
-
'npm-dev'
|
|
157
|
+
'npm-dev',
|
|
158
|
+
],
|
|
159
|
+
// type only dependencies are not a problem as they don't end up in the
|
|
160
|
+
// production code or are ignored by the runtime.
|
|
161
|
+
dependencyTypesNot: [
|
|
162
|
+
'type-only'
|
|
163
|
+
],
|
|
164
|
+
pathNot: [
|
|
165
|
+
'node_modules/@types/'
|
|
158
166
|
]
|
|
159
167
|
}
|
|
160
168
|
},
|
|
@@ -10,7 +10,7 @@ var title2ElementMap = (function makeElementMap() {
|
|
|
10
10
|
|
|
11
11
|
function getHoverHandler(pTitle2ElementMap) {
|
|
12
12
|
/** @type {string} */
|
|
13
|
-
var currentHighlightedTitle;
|
|
13
|
+
var currentHighlightedTitle = "";
|
|
14
14
|
|
|
15
15
|
/** @param {MouseEvent} pMouseEvent */
|
|
16
16
|
return function hoverHighlightHandler(pMouseEvent) {
|
|
@@ -31,7 +31,7 @@ function getHoverHandler(pTitle2ElementMap) {
|
|
|
31
31
|
|
|
32
32
|
function getSelectHandler(pTitle2ElementMap) {
|
|
33
33
|
/** @type {string} */
|
|
34
|
-
var currentHighlightedTitle;
|
|
34
|
+
var currentHighlightedTitle = "";
|
|
35
35
|
|
|
36
36
|
/** @param {MouseEvent} pMouseEvent */
|
|
37
37
|
return function selectHighlightHandler(pMouseEvent) {
|
|
@@ -64,6 +64,9 @@ function Mode() {
|
|
|
64
64
|
this._mode = SELECT;
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
+
/**
|
|
68
|
+
* @returns {number}
|
|
69
|
+
*/
|
|
67
70
|
function get() {
|
|
68
71
|
return this._mode || HOVER;
|
|
69
72
|
}
|
|
@@ -210,23 +213,23 @@ function addHighlight(pGroup) {
|
|
|
210
213
|
}
|
|
211
214
|
}
|
|
212
215
|
|
|
213
|
-
var
|
|
216
|
+
var gHints = {
|
|
214
217
|
HIDDEN: 1,
|
|
215
218
|
SHOWN: 2,
|
|
216
219
|
state: this.HIDDEN,
|
|
217
220
|
show: function () {
|
|
218
221
|
document.getElementById("hints").removeAttribute("style");
|
|
219
|
-
|
|
222
|
+
gHints.state = gHints.SHOWN;
|
|
220
223
|
},
|
|
221
224
|
hide: function () {
|
|
222
225
|
document.getElementById("hints").style = "display:none";
|
|
223
|
-
|
|
226
|
+
gHints.state = gHints.HIDDEN;
|
|
224
227
|
},
|
|
225
228
|
toggle: function () {
|
|
226
|
-
if ((
|
|
227
|
-
|
|
229
|
+
if ((gHints.state || gHints.HIDDEN) === gHints.HIDDEN) {
|
|
230
|
+
gHints.show();
|
|
228
231
|
} else {
|
|
229
|
-
|
|
232
|
+
gHints.hide();
|
|
230
233
|
}
|
|
231
234
|
},
|
|
232
235
|
};
|
|
@@ -236,16 +239,48 @@ function keyboardEventHandler(pKeyboardEvent) {
|
|
|
236
239
|
if (pKeyboardEvent.key === "Escape") {
|
|
237
240
|
resetNodesAndEdges();
|
|
238
241
|
gMode.setToHover();
|
|
239
|
-
|
|
242
|
+
gHints.hide();
|
|
240
243
|
}
|
|
241
244
|
if (pKeyboardEvent.key === "F1") {
|
|
242
245
|
pKeyboardEvent.preventDefault();
|
|
243
|
-
|
|
246
|
+
gHints.toggle();
|
|
244
247
|
}
|
|
245
248
|
}
|
|
246
249
|
|
|
247
250
|
document.addEventListener("contextmenu", getSelectHandler(title2ElementMap));
|
|
248
251
|
document.addEventListener("mouseover", getHoverHandler(title2ElementMap));
|
|
249
252
|
document.addEventListener("keydown", keyboardEventHandler);
|
|
250
|
-
document.getElementById("close-hints").addEventListener("click",
|
|
251
|
-
document.getElementById("button_help").addEventListener("click",
|
|
253
|
+
document.getElementById("close-hints").addEventListener("click", gHints.hide);
|
|
254
|
+
document.getElementById("button_help").addEventListener("click", gHints.toggle);
|
|
255
|
+
document.querySelector("svg").insertAdjacentHTML(
|
|
256
|
+
"afterbegin",
|
|
257
|
+
`<linearGradient id="edgeGradient">
|
|
258
|
+
<stop offset="0%" stop-color="fuchsia"/>
|
|
259
|
+
<stop offset="100%" stop-color="purple"/>
|
|
260
|
+
</linearGradient>
|
|
261
|
+
`,
|
|
262
|
+
);
|
|
263
|
+
|
|
264
|
+
// Add a small increment to the last value of the path to make gradients on
|
|
265
|
+
// horizontal paths work. Without them all browsers I tested with (firefox,
|
|
266
|
+
// chrome) do not render the gradient, but instead make the line transparent
|
|
267
|
+
// (or the color of the background, I haven't looked into it that deeply,
|
|
268
|
+
// but for the hack it doesn't matter which).
|
|
269
|
+
function skewLineABit(lDrawingInstructions) {
|
|
270
|
+
var lLastValue = lDrawingInstructions.match(/(\d+\.?\d*)$/)[0];
|
|
271
|
+
// Smaller values than .001 _should_ work as well, but don't in all
|
|
272
|
+
// cases. Even this value is so small that it is not visible to the
|
|
273
|
+
// human eye (tested with the two I have at my disposal).
|
|
274
|
+
var lIncrement = 0.001;
|
|
275
|
+
var lNewLastValue = parseFloat(lLastValue) + lIncrement;
|
|
276
|
+
|
|
277
|
+
return lDrawingInstructions.replace(lLastValue, lNewLastValue);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
nodeListToArray(document.querySelectorAll("path"))
|
|
281
|
+
.filter(function (pElement) {
|
|
282
|
+
return pElement.parentElement.classList.contains("edge");
|
|
283
|
+
})
|
|
284
|
+
.forEach(function (pElement) {
|
|
285
|
+
pElement.attributes.d.value = skewLineABit(pElement.attributes.d.value);
|
|
286
|
+
});
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
.node:active path,
|
|
2
|
+
.node:hover path,
|
|
3
|
+
.node.current path,
|
|
4
|
+
.node:active polygon,
|
|
5
|
+
.node:hover polygon,
|
|
6
|
+
.node.current polygon {
|
|
7
|
+
stroke: fuchsia;
|
|
8
|
+
stroke-width: 2;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
.edge:active path,
|
|
12
|
+
.edge:hover path,
|
|
13
|
+
.edge.current path,
|
|
14
|
+
.edge:active ellipse,
|
|
15
|
+
.edge:hover ellipse,
|
|
16
|
+
.edge.current ellipse {
|
|
17
|
+
stroke: url(#edgeGradient);
|
|
18
|
+
stroke-width: 3;
|
|
19
|
+
stroke-opacity: 1;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.edge:active polygon,
|
|
23
|
+
.edge:hover polygon,
|
|
24
|
+
.edge.current polygon {
|
|
25
|
+
stroke: fuchsia;
|
|
26
|
+
stroke-width: 3;
|
|
27
|
+
fill: fuchsia;
|
|
28
|
+
stroke-opacity: 1;
|
|
29
|
+
fill-opacity: 1;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.edge:active text,
|
|
33
|
+
.edge:hover text {
|
|
34
|
+
fill: fuchsia;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.cluster path {
|
|
38
|
+
stroke-width: 3;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.cluster:active path,
|
|
42
|
+
.cluster:hover path {
|
|
43
|
+
fill: #ffff0011;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
div.hint {
|
|
47
|
+
background-color: #000000aa;
|
|
48
|
+
color: white;
|
|
49
|
+
font-family: Arial, Helvetica, sans-serif;
|
|
50
|
+
border-radius: 1rem;
|
|
51
|
+
position: fixed;
|
|
52
|
+
top: calc(50% - 4em);
|
|
53
|
+
right: calc(50% - 10em);
|
|
54
|
+
border: none;
|
|
55
|
+
padding: 1em 3em 1em 1em;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.hint button {
|
|
59
|
+
position: absolute;
|
|
60
|
+
font-weight: bolder;
|
|
61
|
+
right: 0.6em;
|
|
62
|
+
top: 0.6em;
|
|
63
|
+
color: inherit;
|
|
64
|
+
background-color: inherit;
|
|
65
|
+
border: 1px solid currentColor;
|
|
66
|
+
border-radius: 1em;
|
|
67
|
+
margin-left: 0.6em;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.hint a {
|
|
71
|
+
color: inherit;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
#button_help {
|
|
75
|
+
color: white;
|
|
76
|
+
background-color: #00000011;
|
|
77
|
+
border-radius: 1em;
|
|
78
|
+
position: fixed;
|
|
79
|
+
top: 1em;
|
|
80
|
+
right: 1em;
|
|
81
|
+
font-size: 24pt;
|
|
82
|
+
font-weight: bolder;
|
|
83
|
+
width: 2em;
|
|
84
|
+
height: 2em;
|
|
85
|
+
border: none;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
#button_help:hover {
|
|
89
|
+
cursor: pointer;
|
|
90
|
+
background-color: #00000077;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
@media print {
|
|
94
|
+
#button_help {
|
|
95
|
+
display: none;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
div.hint {
|
|
99
|
+
display: none;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
@@ -1,19 +1,56 @@
|
|
|
1
1
|
import { readFile } from "node:fs/promises";
|
|
2
2
|
import { fileURLToPath } from "node:url";
|
|
3
3
|
|
|
4
|
-
const
|
|
5
|
-
new URL("svg-in-html-snippets/
|
|
4
|
+
const STYLESHEET_FILE = fileURLToPath(
|
|
5
|
+
new URL("svg-in-html-snippets/style.css", import.meta.url),
|
|
6
6
|
);
|
|
7
7
|
const SCRIPT_FILE = fileURLToPath(
|
|
8
|
-
new URL("svg-in-html-snippets/script.
|
|
9
|
-
);
|
|
10
|
-
const FOOTER_FILE = fileURLToPath(
|
|
11
|
-
new URL("svg-in-html-snippets/footer.snippet.html", import.meta.url)
|
|
8
|
+
new URL("svg-in-html-snippets/script.js", import.meta.url),
|
|
12
9
|
);
|
|
13
10
|
|
|
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
|
+
|
|
14
51
|
/**
|
|
15
52
|
* Slaps the stuff in the passed stream in between the contents
|
|
16
|
-
* of the header and the footer
|
|
53
|
+
* of the header and the footer and returns it as a string.
|
|
17
54
|
*
|
|
18
55
|
* Almost exactly the same as:
|
|
19
56
|
* ```sh
|
|
@@ -26,18 +63,15 @@ const FOOTER_FILE = fileURLToPath(
|
|
|
26
63
|
* @param {writeStream} pOutStream stream to write to
|
|
27
64
|
*/
|
|
28
65
|
export default async function wrap(pInStream, pOutStream) {
|
|
29
|
-
const [
|
|
30
|
-
readFile(
|
|
66
|
+
const [lStylesheet, lScript] = await Promise.all([
|
|
67
|
+
readFile(STYLESHEET_FILE, "utf8"),
|
|
31
68
|
readFile(SCRIPT_FILE, "utf8"),
|
|
32
|
-
readFile(FOOTER_FILE, "utf8"),
|
|
33
69
|
]);
|
|
34
70
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
pOutStream.write(lHeader);
|
|
71
|
+
pOutStream.write(getHeader(lStylesheet));
|
|
38
72
|
pInStream
|
|
39
73
|
.on("end", () => {
|
|
40
|
-
pOutStream.write(
|
|
74
|
+
pOutStream.write(getFooter(lScript));
|
|
41
75
|
pOutStream.end();
|
|
42
76
|
})
|
|
43
77
|
.on(
|
|
@@ -45,9 +79,17 @@ export default async function wrap(pInStream, pOutStream) {
|
|
|
45
79
|
/* c8 ignore start */
|
|
46
80
|
(pError) => {
|
|
47
81
|
process.stderr.write(`${pError}\n`);
|
|
48
|
-
}
|
|
82
|
+
},
|
|
49
83
|
/* c8 ignore stop */
|
|
50
84
|
)
|
|
85
|
+
/*
|
|
86
|
+
* We could have put the whole html in a template literal, but we don't know
|
|
87
|
+
* how large the svg that's going to be injected is going to be - it could just
|
|
88
|
+
* as well be as large that if we're going to buffer it, it's going into out
|
|
89
|
+
* of memory territory.
|
|
90
|
+
*
|
|
91
|
+
* We circumvent that by streaming the svg in between the header and the footer.
|
|
92
|
+
*/
|
|
51
93
|
.on("data", (pChunk) => {
|
|
52
94
|
pOutStream.write(pChunk);
|
|
53
95
|
});
|