Skip to content

Commit 76508e3

Browse files
authored
feature(init): adds extensions detection to one shot configs (#712)
1 parent 41461f4 commit 76508e3

38 files changed

+307
-31
lines changed

.dependency-cruiser.json

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,8 @@
4141
"^fs$",
4242
"^path$",
4343
"$1",
44-
"^src/meta.js$"
44+
"^src/meta.js$",
45+
"^src/extract/transpile/meta.js$"
4546
]
4647
}
4748
},
@@ -299,11 +300,15 @@
299300
"extensions": [
300301
".js",
301302
// ".cjs",
302-
// ".mjs",
303+
".mjs",
303304
// ".jsx",
304305
// ".ts",
306+
// ".cts",
307+
// ".mts",
305308
// ".tsx",
306309
".d.ts"
310+
// ".d.cts",
311+
// ".d.mts",
307312
// ".coffee",
308313
// ".litcoffee",
309314
// "cofee.md",

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,3 +36,4 @@ yarn.lock
3636

3737
# integration test intermediate files
3838
test/integration/*.testing-ground
39+
test/extract/__mocks__/symlinked

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@
9494
"depcruise:graph:view:diff": "node ./bin/dependency-cruise.js bin src test --prefix vscode://file/$(pwd)/ --config configs/.dependency-cruiser-unlimited.json --output-type dot --progress cli-feedback --reaches \"$(watskeburt develop)\" | dot -T svg | node ./bin/wrap-stream-in-html.js | browser",
9595
"depcruise:report": "node ./bin/dependency-cruise.js src bin test configs types --output-type err-html --config configs/.dependency-cruiser-show-metrics-config.json --output-to dependency-violations.html",
9696
"depcruise:report:view": "node ./bin/dependency-cruise.js src bin test configs types --output-type err-html --config configs/.dependency-cruiser-show-metrics-config.json --output-to - | browser",
97-
"depcruise:focus": "node ./bin/dependency-cruise.js src bin test configs types tools --progress --config configs/.dependency-cruiser-show-metrics-config.json --output-type text --focus",
97+
"depcruise:focus": "node ./bin/dependency-cruise.js src bin test configs types tools --progress --config --output-type text --focus",
9898
"depcruise:reaches": "node ./bin/dependency-cruise.js src bin test configs types tools --progress --config configs/.dependency-cruiser-unlimited.json --output-type text --reaches",
9999
"format": "prettier --loglevel warn --write \"src/**/*.js\" \"configs/**/*.js\" \"tools/**/*.mjs\" \"bin/*\" \"types/*.d.ts\" \"test/**/*.spec.{cjs,js}\" \"test/**/*.{spec,utl}.mjs\"",
100100
"format:check": "prettier --loglevel warn --check \"src/**/*.js\" \"configs/**/*.js\" \"tools/**/*.mjs\" \"bin/*\" \"types/*.d.ts\" \"test/**/*.spec.{cjs,js}\" \"test/**/*.{spec,utl}.mjs\"",

src/cli/init-config/build-config.js

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,29 @@
1+
// @ts-check
12
const Handlebars = require("handlebars/runtime");
2-
33
const { folderNameArrayToRE } = require("./utl");
44

55
/* eslint import/no-unassigned-import: 0 */
66
require("./config.js.template");
77

8+
/**
9+
* @param {string} pString
10+
* @returns {string}
11+
*/
12+
function quote(pString) {
13+
return `"${pString}"`;
14+
}
15+
16+
/**
17+
* @param {string[]=} pExtensions
18+
* @returns {string}
19+
*/
20+
function extensionsToString(pExtensions) {
21+
if (pExtensions) {
22+
return `[${pExtensions.map(quote).join(", ")}]`;
23+
}
24+
return "";
25+
}
26+
827
/**
928
* Creates a .dependency-cruiser config with a set of basic validations
1029
* to the current directory.
@@ -22,5 +41,8 @@ module.exports = function buildConfig(pNormalizedInitOptions) {
2241
pNormalizedInitOptions.sourceLocation
2342
),
2443
testLocationRE: folderNameArrayToRE(pNormalizedInitOptions.testLocation),
44+
resolutionExtensionsAsString: extensionsToString(
45+
pNormalizedInitOptions.resolutionExtensions
46+
),
2547
});
2648
};

src/cli/init-config/config.js.template.hbs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -398,7 +398,7 @@ module.exports = {
398398
If you have a 'conditionNames' attribute in your webpack config, that one will
399399
have precedence over the one specified here.
400400
*/
401-
conditionNames: ["import", "require", "node", "default"]
401+
conditionNames: ["import", "require", "node", "default"],
402402
/*
403403
The extensions, by default are the same as the ones dependency-cruiser
404404
can access (run `npx depcruise --info` to see which ones that are in
@@ -408,7 +408,11 @@ module.exports = {
408408
[".js", ".jsx"]). This can speed up the most expensive step in
409409
dependency cruising (module resolution) quite a bit.
410410
*/
411+
{{#if specifyResolutionExtensions}}
412+
extensions: {{{resolutionExtensionsAsString}}},
413+
{{^}}
411414
// extensions: [".js", ".jsx", ".ts", ".tsx", ".d.ts"]
415+
{{/if}}
412416
},
413417
reporterOptions: {
414418
dot: {

src/cli/init-config/config.js.template.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/cli/init-config/environment-helpers.js

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
// @ts-check
12
const { readFileSync, readdirSync, accessSync, statSync, R_OK } = require("fs");
23
const { join } = require("path");
34
const has = require("lodash/has");
@@ -15,8 +16,8 @@ const BABEL_CONFIG_CANDIDATE_PATTERN = /^\.babelrc$|.*babel.*\.json/gi;
1516
/**
1617
* Read the package manifest ('package.json') and return it as a javascript object
1718
*
18-
* @param {string} pManifestFileName - the file name where the package manifest (package.json) lives
19-
* @returns {any} - the contents of said manifest as a javascript object
19+
* @param {import("fs").PathOrFileDescriptor} pManifestFileName - the file name where the package manifest (package.json) lives
20+
* @returns {Record<string,any>} - the contents of said manifest as a javascript object
2021
* @throws {ENOENT} when the manifest wasn't found
2122
* @throws {SyntaxError} when the manifest's json is invalid
2223
*/
@@ -40,6 +41,9 @@ function fileExists(pFile) {
4041
return true;
4142
}
4243

44+
/**
45+
* @returns {boolean}
46+
*/
4347
function babelIsConfiguredInManifest() {
4448
let lReturnValue = false;
4549

@@ -51,6 +55,9 @@ function babelIsConfiguredInManifest() {
5155
return lReturnValue;
5256
}
5357

58+
/**
59+
* @returns {boolean}
60+
*/
5461
function isTypeModule() {
5562
let lReturnValue = false;
5663

@@ -63,12 +70,21 @@ function isTypeModule() {
6370
return lReturnValue;
6471
}
6572

73+
/**
74+
* @param {string} pFolderName
75+
* @returns {string[]} Array of folder names
76+
*/
6677
function getFolderNames(pFolderName) {
6778
return readdirSync(pFolderName, "utf8").filter((pFileName) =>
6879
statSync(join(pFolderName, pFileName)).isDirectory()
6980
);
7081
}
7182

83+
/**
84+
* @param {RegExp} pPattern
85+
* @param {string=} pFolderName
86+
* @returns {string[]}
87+
*/
7288
function getMatchingFileNames(pPattern, pFolderName = process.cwd()) {
7389
return readdirSync(pFolderName, "utf8").filter(
7490
(pFileName) =>
@@ -77,6 +93,10 @@ function getMatchingFileNames(pPattern, pFolderName = process.cwd()) {
7793
);
7894
}
7995

96+
/**
97+
* @param {string[]} pFolderNames
98+
* @returns {boolean}
99+
*/
80100
function isLikelyMonoRepo(pFolderNames = getFolderNames(process.cwd())) {
81101
return pFolderNames.includes("packages");
82102
}
@@ -95,13 +115,20 @@ function getFolderCandidates(pCandidateFolderArray) {
95115
};
96116
}
97117

118+
/**
119+
* @param {string[]|string} pLocations
120+
* @returns {string[]}
121+
*/
98122
function toSourceLocationArray(pLocations) {
99123
if (!Array.isArray(pLocations)) {
100124
return pLocations.split(",").map((pFolder) => pFolder.trim());
101125
}
102126
return pLocations;
103127
}
104128

129+
/**
130+
* @returns {string[]}
131+
*/
105132
function getManifestFilesWithABabelConfig() {
106133
return babelIsConfiguredInManifest() ? ["package.json"] : [];
107134
}
@@ -132,6 +159,10 @@ const getTestFolderCandidates = getFolderCandidates(LIKELY_TEST_FOLDERS);
132159
const getMonoRepoPackagesCandidates = getFolderCandidates(
133160
LIKELY_PACKAGES_FOLDERS
134161
);
162+
163+
/**
164+
* @returns {string}
165+
*/
135166
function getDefaultConfigFileName() {
136167
return isTypeModule() ? ".dependency-cruiser.cjs" : DEFAULT_CONFIG_FILE_NAME;
137168
}
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
// @ts-check
2+
/* eslint-disable security/detect-object-injection */
3+
const fs = require("fs");
4+
const path = require("path");
5+
const pathToPosix = require("../../utl/path-to-posix");
6+
const getExtension = require("../../utl/get-extension.js");
7+
const meta = require("../../extract/transpile/meta");
8+
9+
/**
10+
* @param {string[]} pIgnorablePathElements
11+
* @returns {(string) => boolean}
12+
*/
13+
function notIgnorable(pIgnorablePathElements) {
14+
return (pPath) => {
15+
return !pIgnorablePathElements.includes(pPath);
16+
};
17+
}
18+
19+
/**
20+
* @param {string} pFullPathToFile
21+
* @param {string} pBaseDirectory
22+
* @returns {boolean}
23+
*/
24+
function fileIsDirectory(pFullPathToFile, pBaseDirectory) {
25+
try {
26+
const lStat = fs.statSync(path.join(pBaseDirectory, pFullPathToFile));
27+
return lStat.isDirectory();
28+
} catch (pError) {
29+
return false;
30+
}
31+
}
32+
33+
/**
34+
* @param {string} pDirectoryName
35+
* @param {{baseDir: string; ignorablePathElements: string[]}} pOptions
36+
* @returns {string[]}
37+
*/
38+
function listAllModules(pDirectoryName, { baseDir, ignorablePathElements }) {
39+
return fs
40+
.readdirSync(path.join(baseDir, pDirectoryName))
41+
.filter(notIgnorable(ignorablePathElements))
42+
.map((pFileName) => path.join(pDirectoryName, pFileName))
43+
.map((pFullPathToFile) => ({
44+
fullPathToFile: pFullPathToFile,
45+
isDirectory: fileIsDirectory(pFullPathToFile, baseDir),
46+
}))
47+
.reduce(
48+
/**
49+
* @param {string[]} pSum
50+
* @param {{fullPathToFile: string; isDirectory: boolean}} pCurrentValue
51+
* @returns {string[]}
52+
*/
53+
(pSum, { fullPathToFile, isDirectory }) => {
54+
if (isDirectory) {
55+
return pSum.concat(
56+
listAllModules(fullPathToFile, { baseDir, ignorablePathElements })
57+
);
58+
}
59+
return pSum.concat(fullPathToFile);
60+
},
61+
[]
62+
)
63+
.map((pFullPathToFile) => pathToPosix(pFullPathToFile));
64+
}
65+
66+
/**
67+
* @param {Record<string,number>} pAll
68+
* @param {string} pExtension
69+
*/
70+
function reduceToCounts(pAll, pExtension) {
71+
if (pAll[pExtension]) {
72+
pAll[pExtension] += 1;
73+
} else {
74+
pAll[pExtension] = 1;
75+
}
76+
return pAll;
77+
}
78+
79+
function compareByCount(pCountsObject) {
80+
return function compare(pLeft, pRight) {
81+
return pCountsObject[pRight] - pCountsObject[pLeft];
82+
};
83+
}
84+
85+
/**
86+
* @param {string[]} pDirectories
87+
* @param {{baseDir?: string; ignorablePathElements?: string[], scannableExtensions?: string[]}=} pOptions
88+
* @returns {string[]}
89+
*/
90+
module.exports = function findExtensions(pDirectories, pOptions) {
91+
const lOptions = {
92+
baseDir: process.cwd(),
93+
ignorablePathElements: [
94+
".git",
95+
".husky",
96+
".vscode",
97+
"coverage",
98+
"node_nodules",
99+
"nyc",
100+
],
101+
scannableExtensions: meta.scannableExtensions,
102+
...pOptions,
103+
};
104+
105+
const lExtensionsWithCounts = pDirectories
106+
.flatMap((pDirectory) =>
107+
listAllModules(pDirectory, lOptions).map(getExtension).filter(Boolean)
108+
)
109+
.reduce(reduceToCounts, {});
110+
111+
return Object.keys(lExtensionsWithCounts)
112+
.filter((pExtension) => lOptions.scannableExtensions.includes(pExtension))
113+
.sort(compareByCount(lExtensionsWithCounts));
114+
};

src/cli/init-config/get-user-input.js

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
// @ts-check
12
const prompts = require("prompts");
23
const {
34
isLikelyMonoRepo,
@@ -34,9 +35,9 @@ const QUESTIONS = [
3435
},
3536
{
3637
name: "sourceLocation",
37-
type: (_, pAnswers) => (pAnswers.isMonoRepo ? "text" : false),
38+
type: (_, pAnswers) => (pAnswers.isMonoRepo ? "list" : false),
3839
message: "Mono repo it is! Where do your packages live?",
39-
initial: getMonoRepoPackagesCandidates(),
40+
initial: getMonoRepoPackagesCandidates().join(", "),
4041
validate: validateLocation,
4142
},
4243
{
@@ -48,9 +49,9 @@ const QUESTIONS = [
4849
},
4950
{
5051
name: "sourceLocation",
51-
type: (_, pAnswers) => (pAnswers.isMonoRepo ? false : "text"),
52+
type: (_, pAnswers) => (pAnswers.isMonoRepo ? false : "list"),
5253
message: "Where do your source files live?",
53-
initial: getSourceFolderCandidates(),
54+
initial: getSourceFolderCandidates().join(", "),
5455
validate: validateLocation,
5556
},
5657
{
@@ -67,9 +68,9 @@ const QUESTIONS = [
6768
{
6869
name: "testLocation",
6970
type: (_, pAnswers) =>
70-
pAnswers.hasTestsOutsideSource && !pAnswers.isMonoRepo ? "text" : false,
71+
pAnswers.hasTestsOutsideSource && !pAnswers.isMonoRepo ? "list" : false,
7172
message: "Where do your test files live?",
72-
initial: getTestFolderCandidates(),
73+
initial: getTestFolderCandidates().join(", "),
7374
validate: validateLocation,
7475
},
7576
{

src/cli/init-config/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ function getOneShotConfig(pOneShotConfigId) {
4444
webpackConfig: getWebpackConfigCandidates().shift(),
4545
useBabelConfig: hasBabelConfigCandidates(),
4646
babelConfig: getBabelConfigCandidates().shift(),
47+
specifyResolutionExtensions: true,
4748
};
4849
/** @type {Record<import("./types").OneShotConfigIDType, import("./types").IPartialInitConfig>} */
4950
const lOneShotConfigs = {

0 commit comments

Comments
 (0)