@noahnu/unused-files 0.0.1 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +15 -1
- package/README.md +8 -3
- package/lib/api/index.d.ts +13 -9
- package/lib/api/index.js +30 -14
- package/lib/api/types.d.ts +15 -0
- package/lib/api/types.js +2 -0
- package/lib/api/walkDependencyTree.d.ts +3 -2
- package/lib/api/walkDependencyTree.js +16 -10
- package/lib/bin.js +1 -1
- package/lib/command.js +1 -1
- package/package.json +7 -6
package/CHANGELOG.md
CHANGED
@@ -1,6 +1,20 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
-
<!--
|
3
|
+
<!-- MONOWEAVE:BELOW -->
|
4
|
+
|
5
|
+
## @noahnu/unused-files (v0.1.0) <a name="0.1.0"></a>
|
6
|
+
|
7
|
+
Support custom resolvers.
|
8
|
+
|
9
|
+
|
10
|
+
|
11
|
+
## @noahnu/unused-files (v0.0.2) <a name="0.0.2"></a>
|
12
|
+
|
13
|
+
Update ESLint config and plugins to ESLint v9.
|
14
|
+
|
15
|
+
Update misc. dependencies.
|
16
|
+
|
17
|
+
|
4
18
|
|
5
19
|
## [0.0.1](https://github.com/noahnu/nodejs-tools/compare/@noahnu/unused-files@0.0.0...@noahnu/unused-files@0.0.1) "@noahnu/unused-files" (2024-01-15)<a name="0.0.1"></a>
|
6
20
|
|
package/README.md
CHANGED
@@ -17,9 +17,14 @@ const result = await findUnusedFiles({
|
|
17
17
|
// optional
|
18
18
|
sourceDirectories: [process.cwd()],
|
19
19
|
ignorePatterns: ['**/node_modules'],
|
20
|
-
|
21
|
-
|
22
|
-
|
20
|
+
resolvers: [
|
21
|
+
async ({ request, context }) => {
|
22
|
+
if (request === '@my/alias') {
|
23
|
+
return { result: 'path/to/file/index.ts' }
|
24
|
+
}
|
25
|
+
return null;
|
26
|
+
}
|
27
|
+
],
|
23
28
|
depth: 10,
|
24
29
|
})
|
25
30
|
|
package/lib/api/index.d.ts
CHANGED
@@ -1,4 +1,6 @@
|
|
1
|
-
|
1
|
+
import { type Resolver } from './types';
|
2
|
+
export type { Resolver, ResolverResult, ResolverParams } from './types';
|
3
|
+
export interface FindUnusedFilesOptions {
|
2
4
|
/**
|
3
5
|
* Entry files into the codebase. These files are known to be used and any files that
|
4
6
|
* are dependencies of these entry files are also considered used files.
|
@@ -14,17 +16,19 @@ export type FindUnusedFilesOptions = {
|
|
14
16
|
*/
|
15
17
|
ignorePatterns?: string[];
|
16
18
|
/**
|
17
|
-
* Custom
|
18
|
-
* It is recommended to rely on package.json aliases over these custom ones
|
19
|
+
* Custom resolver to use to resolve an import path relative to a source file.
|
20
|
+
* It is recommended to rely on package.json aliases over these custom ones
|
21
|
+
* when possible.
|
19
22
|
*/
|
20
|
-
|
23
|
+
resolvers?: Resolver[];
|
21
24
|
/**
|
22
25
|
* Maximum depth to traverse. -1 can be used to disable the depth limit (the default).
|
23
26
|
*/
|
24
27
|
depth?: number;
|
25
28
|
cwd?: string;
|
26
|
-
}
|
27
|
-
export
|
28
|
-
|
29
|
-
|
30
|
-
|
29
|
+
}
|
30
|
+
export interface UnusedFilesResult {
|
31
|
+
used: string[];
|
32
|
+
unused: string[];
|
33
|
+
}
|
34
|
+
export declare function findUnusedFiles({ entryFiles, ignorePatterns, sourceDirectories, resolvers, depth, cwd, }: FindUnusedFilesOptions): Promise<UnusedFilesResult>;
|
package/lib/api/index.js
CHANGED
@@ -3,42 +3,58 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
4
4
|
};
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
6
|
-
exports.findUnusedFiles =
|
6
|
+
exports.findUnusedFiles = findUnusedFiles;
|
7
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
7
8
|
const node_path_1 = __importDefault(require("node:path"));
|
8
9
|
const debug_1 = __importDefault(require("debug"));
|
9
10
|
const fast_glob_1 = __importDefault(require("fast-glob"));
|
10
11
|
const walkDependencyTree_1 = require("./walkDependencyTree");
|
11
12
|
const debug = (0, debug_1.default)('unused-files');
|
12
|
-
async function findUnusedFiles({ entryFiles, ignorePatterns = ['**/node_modules'], sourceDirectories = [],
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
13
|
+
async function findUnusedFiles({ entryFiles, ignorePatterns = ['**/node_modules'], sourceDirectories = [], resolvers, depth, cwd = process.cwd(), }) {
|
14
|
+
cwd = await node_fs_1.default.promises.realpath(cwd);
|
15
|
+
const globFromSource = async (source) => {
|
16
|
+
const files = await fast_glob_1.default.glob(fast_glob_1.default.isDynamicPattern(source) ? source : node_path_1.default.join(source, '**'), {
|
17
|
+
dot: false,
|
18
|
+
ignore: ignorePatterns,
|
19
|
+
absolute: true,
|
20
|
+
cwd,
|
21
|
+
});
|
22
|
+
return Promise.all(files.map(async (file) => await node_fs_1.default.promises.realpath(file).catch(() => file)));
|
23
|
+
};
|
19
24
|
const sourceDirs = sourceDirectories.length > 0 ? sourceDirectories : [cwd];
|
20
25
|
const files = new Set([].concat(...(await Promise.all(sourceDirs.map((source) => globFromSource(source))))));
|
21
26
|
const unvisitedFiles = new Set(files);
|
27
|
+
const visitedFiles = new Set();
|
22
28
|
for (const entryFile of entryFiles) {
|
23
|
-
const entry = node_path_1.default.resolve(cwd, entryFile);
|
29
|
+
const entry = await node_fs_1.default.promises.realpath(node_path_1.default.resolve(cwd, entryFile));
|
24
30
|
unvisitedFiles.delete(entry);
|
25
31
|
for await (const { source, dependency } of (0, walkDependencyTree_1.walkDependencyTree)(entry, {
|
26
|
-
|
32
|
+
resolvers,
|
27
33
|
depth,
|
34
|
+
visited: visitedFiles,
|
28
35
|
})) {
|
36
|
+
let resolvedDependency = dependency;
|
29
37
|
if (files.has(dependency)) {
|
30
38
|
debug(`${source}: ${dependency} [dependency]`);
|
31
39
|
}
|
32
40
|
else {
|
33
|
-
|
41
|
+
const realpath = await node_fs_1.default.promises.realpath(dependency);
|
42
|
+
if (files.has(realpath)) {
|
43
|
+
resolvedDependency = realpath;
|
44
|
+
}
|
45
|
+
else {
|
46
|
+
debug(`${source}: ${dependency} [unknown dependency]`);
|
47
|
+
}
|
34
48
|
}
|
35
|
-
unvisitedFiles.delete(
|
49
|
+
unvisitedFiles.delete(resolvedDependency);
|
36
50
|
}
|
37
51
|
}
|
38
52
|
return {
|
39
|
-
|
53
|
+
unused: Array.from(unvisitedFiles)
|
54
|
+
.map((abspath) => node_path_1.default.relative(cwd, abspath))
|
55
|
+
.sort(),
|
56
|
+
used: Array.from(visitedFiles)
|
40
57
|
.map((abspath) => node_path_1.default.relative(cwd, abspath))
|
41
58
|
.sort(),
|
42
59
|
};
|
43
60
|
}
|
44
|
-
exports.findUnusedFiles = findUnusedFiles;
|
@@ -0,0 +1,15 @@
|
|
1
|
+
export interface ResolverResult {
|
2
|
+
/** Resolved path. */
|
3
|
+
result: string;
|
4
|
+
}
|
5
|
+
export interface ResolverParams {
|
6
|
+
/** The module/path being requested. */
|
7
|
+
request: string;
|
8
|
+
/** The file or directory to resolve the request from. */
|
9
|
+
context: string;
|
10
|
+
}
|
11
|
+
/**
|
12
|
+
* Used to resolve imports to the absolute path of the file.
|
13
|
+
* Return 'null' if unable to resolve and we will attempt the next resolver in the chain.
|
14
|
+
*/
|
15
|
+
export type Resolver = (params: ResolverParams) => Promise<ResolverResult | null>;
|
package/lib/api/types.js
ADDED
@@ -1,5 +1,6 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
import { type Resolver } from './types';
|
2
|
+
export declare function walkDependencyTree(source: string, { resolvers, visited, depth, }?: {
|
3
|
+
resolvers?: Resolver[];
|
3
4
|
visited?: Set<string>;
|
4
5
|
depth?: number;
|
5
6
|
}): AsyncGenerator<{
|
@@ -3,15 +3,15 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
4
4
|
};
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
6
|
-
exports.walkDependencyTree =
|
6
|
+
exports.walkDependencyTree = walkDependencyTree;
|
7
7
|
const node_fs_1 = __importDefault(require("node:fs"));
|
8
8
|
const node_path_1 = __importDefault(require("node:path"));
|
9
9
|
const typescript_estree_1 = require("@typescript-eslint/typescript-estree");
|
10
10
|
const debug_1 = __importDefault(require("debug"));
|
11
11
|
const debug = (0, debug_1.default)('unused-files:parse');
|
12
12
|
const DEFAULT_DEPTH_LIMIT = -1; // no depth limit
|
13
|
-
const VALID_EXTENSIONS = new Set(['ts', 'tsx', 'mts', 'cts', 'js', 'mjs', 'cjs']);
|
14
|
-
async function* walkDependencyTree(source, {
|
13
|
+
const VALID_EXTENSIONS = new Set(['ts', 'tsx', 'mts', 'cts', 'js', 'jsx', 'mjs', 'cjs']);
|
14
|
+
async function* walkDependencyTree(source, { resolvers, visited, depth = DEFAULT_DEPTH_LIMIT, } = {}) {
|
15
15
|
const ext = node_path_1.default.extname(source).substring(1);
|
16
16
|
if (!VALID_EXTENSIONS.has(ext)) {
|
17
17
|
debug(`${source}: Unknown file extension '${ext}' [skipping]`);
|
@@ -59,6 +59,11 @@ async function* walkDependencyTree(source, { aliases, visited, depth = DEFAULT_D
|
|
59
59
|
importFroms.add(node.source.value);
|
60
60
|
}
|
61
61
|
}
|
62
|
+
else if (node.source.type === typescript_estree_1.TSESTree.AST_NODE_TYPES.TemplateLiteral &&
|
63
|
+
!node.source.expressions.length &&
|
64
|
+
node.source.quasis.length === 1) {
|
65
|
+
importFroms.add(node.source.quasis[0].value.cooked);
|
66
|
+
}
|
62
67
|
else {
|
63
68
|
debug(`${source}: Dynamic import expression found at ${node.loc.start}:${node.loc.end}`);
|
64
69
|
}
|
@@ -68,10 +73,12 @@ async function* walkDependencyTree(source, { aliases, visited, depth = DEFAULT_D
|
|
68
73
|
for (const body of ast.body) {
|
69
74
|
(0, typescript_estree_1.simpleTraverse)(body, { visitors });
|
70
75
|
}
|
71
|
-
const resolveToAbsPath = (request) => {
|
72
|
-
const
|
73
|
-
|
74
|
-
|
76
|
+
const resolveToAbsPath = async (request) => {
|
77
|
+
for (const resolver of resolvers ?? []) {
|
78
|
+
const resolution = await resolver({ context: source, request });
|
79
|
+
if (resolution) {
|
80
|
+
return node_path_1.default.resolve(resolution.result);
|
81
|
+
}
|
75
82
|
}
|
76
83
|
try {
|
77
84
|
return require.resolve(request, { paths: [node_path_1.default.dirname(source)] });
|
@@ -80,12 +87,12 @@ async function* walkDependencyTree(source, { aliases, visited, depth = DEFAULT_D
|
|
80
87
|
return undefined;
|
81
88
|
};
|
82
89
|
for (const importFrom of Array.from(importFroms)) {
|
83
|
-
const absPath = resolveToAbsPath(importFrom);
|
90
|
+
const absPath = await resolveToAbsPath(importFrom);
|
84
91
|
if (absPath) {
|
85
92
|
yield { dependency: absPath, source };
|
86
93
|
if (depth === -1 || depth > 0) {
|
87
94
|
yield* walkDependencyTree(absPath, {
|
88
|
-
|
95
|
+
resolvers,
|
89
96
|
visited: visitedSet,
|
90
97
|
depth: depth === -1 ? depth : depth - 1,
|
91
98
|
});
|
@@ -96,4 +103,3 @@ async function* walkDependencyTree(source, { aliases, visited, depth = DEFAULT_D
|
|
96
103
|
}
|
97
104
|
}
|
98
105
|
}
|
99
|
-
exports.walkDependencyTree = walkDependencyTree;
|
package/lib/bin.js
CHANGED
@@ -5,7 +5,7 @@ const command_1 = require("./command");
|
|
5
5
|
const cli = new clipanion_1.Cli({
|
6
6
|
binaryLabel: '@noahnu/unused-files',
|
7
7
|
binaryName: 'yarn @noahnu/unused-files',
|
8
|
-
// eslint-disable-next-line @typescript-eslint/no-
|
8
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
9
9
|
binaryVersion: require('../package.json').version,
|
10
10
|
enableCapture: true,
|
11
11
|
});
|
package/lib/command.js
CHANGED
@@ -57,7 +57,7 @@ class BaseCommand extends clipanion_1.Command {
|
|
57
57
|
? -1
|
58
58
|
: parseInt(Math.max(this.depth, -1).toFixed(0), 10),
|
59
59
|
});
|
60
|
-
this.context.stdout.write(result.
|
60
|
+
this.context.stdout.write(result.unused.join('\n'));
|
61
61
|
}
|
62
62
|
}
|
63
63
|
exports.BaseCommand = BaseCommand;
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@noahnu/unused-files",
|
3
|
-
"version": "0.0
|
3
|
+
"version": "0.1.0",
|
4
4
|
"repository": {
|
5
5
|
"type": "git",
|
6
6
|
"url": "https://github.com/noahnu/nodejs-tools.git",
|
@@ -15,7 +15,7 @@
|
|
15
15
|
"scripts": {
|
16
16
|
"clean": "run workspace:clean \"$(pwd)\"",
|
17
17
|
"prepack": "run workspace:build \"$(pwd)\"",
|
18
|
-
"run-local": "run -T
|
18
|
+
"run-local": "run -T tsx ./src/bin.ts"
|
19
19
|
},
|
20
20
|
"bin": "./lib/bin.js",
|
21
21
|
"main": "./lib/api/index.js",
|
@@ -31,16 +31,17 @@
|
|
31
31
|
],
|
32
32
|
"dependencies": {
|
33
33
|
"@types/debug": "^4.1.12",
|
34
|
-
"@typescript-eslint/typescript-estree": "^
|
35
|
-
"clipanion": "4.0.0-rc.
|
36
|
-
"debug": "^4.3.
|
34
|
+
"@typescript-eslint/typescript-estree": "^8.13.0",
|
35
|
+
"clipanion": "^4.0.0-rc.4",
|
36
|
+
"debug": "^4.3.7",
|
37
37
|
"fast-glob": "^3.3.2",
|
38
38
|
"typanion": "^3.14.0"
|
39
39
|
},
|
40
40
|
"devDependencies": {
|
41
41
|
"@jest/globals": "^29.7.0",
|
42
42
|
"@noahnu/internal-test-utils": "0.0.0",
|
43
|
-
"@types/node": "^
|
43
|
+
"@types/node": "^22.9.0",
|
44
|
+
"typescript": "^5.6.3"
|
44
45
|
},
|
45
46
|
"types": "./lib/api/index.d.ts"
|
46
47
|
}
|