node-modules-tools 0.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE.md ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Anthony Fu <https://github.com/antfu>
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,32 @@
1
+ # node-modules-tools
2
+
3
+ [![npm version][npm-version-src]][npm-version-href]
4
+ [![npm downloads][npm-downloads-src]][npm-downloads-href]
5
+ [![bundle][bundle-src]][bundle-href]
6
+ [![JSDocs][jsdocs-src]][jsdocs-href]
7
+ [![License][license-src]][license-href]
8
+
9
+ ## Sponsors
10
+
11
+ <p align="center">
12
+ <a href="https://cdn.jsdelivr.net/gh/antfu/static/sponsors.svg">
13
+ <img src='https://cdn.jsdelivr.net/gh/antfu/static/sponsors.svg'/>
14
+ </a>
15
+ </p>
16
+
17
+ ## License
18
+
19
+ [MIT](./LICENSE) License © [Anthony Fu](https://github.com/antfu)
20
+
21
+ <!-- Badges -->
22
+
23
+ [npm-version-src]: https://img.shields.io/npm/v/node-modules-tools?style=flat&colorA=080f12&colorB=1fa669
24
+ [npm-version-href]: https://npmjs.com/package/node-modules-tools
25
+ [npm-downloads-src]: https://img.shields.io/npm/dm/node-modules-tools?style=flat&colorA=080f12&colorB=1fa669
26
+ [npm-downloads-href]: https://npmjs.com/package/node-modules-tools
27
+ [bundle-src]: https://img.shields.io/bundlephobia/minzip/node-modules-tools?style=flat&colorA=080f12&colorB=1fa669&label=minzip
28
+ [bundle-href]: https://bundlephobia.com/result?p=node-modules-tools
29
+ [license-src]: https://img.shields.io/github/license/antfu/node-modules-tools.svg?style=flat&colorA=080f12&colorB=1fa669
30
+ [license-href]: https://github.com/antfu/node-modules-tools/blob/main/LICENSE
31
+ [jsdocs-src]: https://img.shields.io/badge/jsdocs-reference-080f12?style=flat&colorA=080f12&colorB=1fa669
32
+ [jsdocs-href]: https://www.jsdocs.io/package/node-modules-tools
@@ -0,0 +1,81 @@
1
+ import { x } from 'tinyexec';
2
+
3
+ async function getDependenciesTree(options) {
4
+ const args = ["ls", "--json", "--no-optional", "--depth", String(options.depth)];
5
+ if (options.monorepo)
6
+ args.push("--recursive");
7
+ const raw = await x("pnpm", args, { throwOnError: true, nodeOptions: { cwd: options.cwd } });
8
+ const tree = JSON.parse(raw.stdout);
9
+ return tree;
10
+ }
11
+ async function listPackageDependencies(options) {
12
+ const tree = await getDependenciesTree(options);
13
+ const specs = /* @__PURE__ */ new Map();
14
+ const map = /* @__PURE__ */ new WeakMap();
15
+ function normalize(raw) {
16
+ let node = map.get(raw);
17
+ if (node)
18
+ return node;
19
+ node = {
20
+ spec: `${raw.from}@${raw.version}`,
21
+ name: raw.from,
22
+ version: raw.version,
23
+ path: raw.path,
24
+ dependencies: /* @__PURE__ */ new Set(),
25
+ dependents: /* @__PURE__ */ new Set(),
26
+ flatDependents: /* @__PURE__ */ new Set(),
27
+ flatDependencies: /* @__PURE__ */ new Set(),
28
+ nestedLevels: /* @__PURE__ */ new Set(),
29
+ dev: false,
30
+ prod: false,
31
+ optional: false
32
+ };
33
+ map.set(raw, node);
34
+ return node;
35
+ }
36
+ function traverse(_node, level, mode, directImporter, nestedImporter) {
37
+ if (_node.from.startsWith("@types"))
38
+ return;
39
+ const node = normalize(_node);
40
+ if (directImporter)
41
+ node.dependents.add(directImporter);
42
+ for (const im of nestedImporter)
43
+ node.flatDependents.add(im);
44
+ node.nestedLevels.add(level);
45
+ if (mode === "dev")
46
+ node.dev = true;
47
+ if (mode === "prod")
48
+ node.prod = true;
49
+ if (mode === "optional")
50
+ node.optional = true;
51
+ if (options.traverseFilter?.(node) === false)
52
+ return;
53
+ if (specs.has(node.spec))
54
+ return;
55
+ specs.set(node.spec, node);
56
+ for (const dep of Object.values(_node.dependencies || {})) {
57
+ traverse(dep, level + 1, mode, node.spec, [...nestedImporter, node.spec]);
58
+ }
59
+ }
60
+ for (const pkg of tree) {
61
+ for (const dep of Object.values(pkg.dependencies || {})) {
62
+ traverse(dep, 1, "prod", undefined, []);
63
+ }
64
+ for (const dep of Object.values(pkg.devDependencies || {})) {
65
+ traverse(dep, 1, "dev", undefined, []);
66
+ }
67
+ }
68
+ const packages = [...specs.values()].sort((a, b) => a.spec.localeCompare(b.spec));
69
+ for (const pkg of packages) {
70
+ for (const dep of pkg.flatDependents) {
71
+ const node = specs.get(dep);
72
+ if (node)
73
+ node.flatDependencies.add(pkg.spec);
74
+ }
75
+ }
76
+ return {
77
+ packages
78
+ };
79
+ }
80
+
81
+ export { listPackageDependencies };
@@ -0,0 +1,69 @@
1
+ type PackageModuleTypeSimple = 'cjs' | 'esm';
2
+ type PackageModuleType = 'cjs' | 'esm' | 'dual' | 'faux';
3
+ interface ListPackageDependenciesOptions {
4
+ /**
5
+ * Current working directory
6
+ */
7
+ cwd: string;
8
+ /**
9
+ * Depeth of the dependency tree
10
+ */
11
+ depth: number;
12
+ /**
13
+ * Should it list dependencies of all packages in the monorepo
14
+ */
15
+ monorepo: boolean;
16
+ /**
17
+ * Filter if a package should be included and continue traversing
18
+ */
19
+ traverseFilter?: (node: PackageNode) => boolean;
20
+ }
21
+ interface ListPackageDependenciesResult {
22
+ packages: PackageNode[];
23
+ }
24
+ interface PackageNode {
25
+ /** Package Name */
26
+ name: string;
27
+ /** Version */
28
+ version: string;
29
+ /** Combined name and version using `@` */
30
+ spec: string;
31
+ /** Absolute file path of the package */
32
+ path: string;
33
+ /** Direct dependencies of this package */
34
+ dependencies: Set<string>;
35
+ /** Direct dependents of this package */
36
+ dependents: Set<string>;
37
+ /** All nested dependencies of this package */
38
+ flatDependencies: Set<string>;
39
+ /** All nested dependents of this package */
40
+ flatDependents: Set<string>;
41
+ /** Nested levels of this package */
42
+ nestedLevels: Set<number>;
43
+ /** Is this package part of devDependencies */
44
+ dev: boolean;
45
+ /** Is this package part of dependencies */
46
+ prod: boolean;
47
+ /** Is this package part of optionalDependencies */
48
+ optional: boolean;
49
+ }
50
+ interface ResolvedPackageNode extends PackageNode {
51
+ module: PackageModuleType;
52
+ }
53
+
54
+ /**
55
+ * Analyze a package node, and return a resolved package node.
56
+ * This function mutates the input package node.
57
+ *
58
+ * - Set `module` to the resolved module type (cjs, esm, dual, faux).
59
+ */
60
+ declare function analyzePackage(pkg: PackageNode): Promise<ResolvedPackageNode>;
61
+
62
+ /**
63
+ * List dependencies of packages in the current project.
64
+ *
65
+ * This function will automatically detect the package manager in the current project, and list the dependencies of the packages.
66
+ */
67
+ declare function listPackageDependencies(options: ListPackageDependenciesOptions): Promise<ListPackageDependenciesResult>;
68
+
69
+ export { type ListPackageDependenciesOptions, type ListPackageDependenciesResult, type PackageModuleType, type PackageModuleTypeSimple, type PackageNode, type ResolvedPackageNode, analyzePackage, listPackageDependencies };
@@ -0,0 +1,69 @@
1
+ type PackageModuleTypeSimple = 'cjs' | 'esm';
2
+ type PackageModuleType = 'cjs' | 'esm' | 'dual' | 'faux';
3
+ interface ListPackageDependenciesOptions {
4
+ /**
5
+ * Current working directory
6
+ */
7
+ cwd: string;
8
+ /**
9
+ * Depeth of the dependency tree
10
+ */
11
+ depth: number;
12
+ /**
13
+ * Should it list dependencies of all packages in the monorepo
14
+ */
15
+ monorepo: boolean;
16
+ /**
17
+ * Filter if a package should be included and continue traversing
18
+ */
19
+ traverseFilter?: (node: PackageNode) => boolean;
20
+ }
21
+ interface ListPackageDependenciesResult {
22
+ packages: PackageNode[];
23
+ }
24
+ interface PackageNode {
25
+ /** Package Name */
26
+ name: string;
27
+ /** Version */
28
+ version: string;
29
+ /** Combined name and version using `@` */
30
+ spec: string;
31
+ /** Absolute file path of the package */
32
+ path: string;
33
+ /** Direct dependencies of this package */
34
+ dependencies: Set<string>;
35
+ /** Direct dependents of this package */
36
+ dependents: Set<string>;
37
+ /** All nested dependencies of this package */
38
+ flatDependencies: Set<string>;
39
+ /** All nested dependents of this package */
40
+ flatDependents: Set<string>;
41
+ /** Nested levels of this package */
42
+ nestedLevels: Set<number>;
43
+ /** Is this package part of devDependencies */
44
+ dev: boolean;
45
+ /** Is this package part of dependencies */
46
+ prod: boolean;
47
+ /** Is this package part of optionalDependencies */
48
+ optional: boolean;
49
+ }
50
+ interface ResolvedPackageNode extends PackageNode {
51
+ module: PackageModuleType;
52
+ }
53
+
54
+ /**
55
+ * Analyze a package node, and return a resolved package node.
56
+ * This function mutates the input package node.
57
+ *
58
+ * - Set `module` to the resolved module type (cjs, esm, dual, faux).
59
+ */
60
+ declare function analyzePackage(pkg: PackageNode): Promise<ResolvedPackageNode>;
61
+
62
+ /**
63
+ * List dependencies of packages in the current project.
64
+ *
65
+ * This function will automatically detect the package manager in the current project, and list the dependencies of the packages.
66
+ */
67
+ declare function listPackageDependencies(options: ListPackageDependenciesOptions): Promise<ListPackageDependenciesResult>;
68
+
69
+ export { type ListPackageDependenciesOptions, type ListPackageDependenciesResult, type PackageModuleType, type PackageModuleTypeSimple, type PackageNode, type ResolvedPackageNode, analyzePackage, listPackageDependencies };
package/dist/index.mjs ADDED
@@ -0,0 +1,125 @@
1
+ import fs from 'node:fs/promises';
2
+ import { join } from 'node:path';
3
+ import { detect } from 'package-manager-detector';
4
+
5
+ function analyzePackageJson(pkgJson) {
6
+ const { exports, main, type } = pkgJson;
7
+ let cjs;
8
+ let esm;
9
+ let fauxEsm;
10
+ if (pkgJson.module) {
11
+ fauxEsm = true;
12
+ }
13
+ if (exports && typeof exports === "object") {
14
+ for (const exportId in exports) {
15
+ if (Object.hasOwn(exports, exportId) && typeof exportId === "string") {
16
+ const value = (
17
+ /** @type {unknown} */
18
+ exports[exportId]
19
+ );
20
+ analyzeThing(value, `${pkgJson.name}#exports`);
21
+ }
22
+ }
23
+ }
24
+ if (esm && type === "commonjs") {
25
+ cjs = true;
26
+ }
27
+ if (cjs && type === "module") {
28
+ esm = true;
29
+ }
30
+ if (cjs === undefined && esm === undefined) {
31
+ if (type === "module" || main && /\.mjs$/.test(main)) {
32
+ esm = true;
33
+ } else {
34
+ cjs = true;
35
+ }
36
+ }
37
+ const style = esm && cjs ? "dual" : esm ? "esm" : fauxEsm ? "faux" : "cjs";
38
+ return style;
39
+ function analyzeThing(value, path) {
40
+ if (value && typeof value === "object") {
41
+ if (Array.isArray(value)) {
42
+ const values = (
43
+ /** @type {Array<unknown>} */
44
+ value
45
+ );
46
+ let index = -1;
47
+ while (++index < values.length) {
48
+ analyzeThing(values[index], `${path}[${index}]`);
49
+ }
50
+ } else {
51
+ const record = (
52
+ /** @type {Record<string, unknown>} */
53
+ value
54
+ );
55
+ let dots = false;
56
+ for (const [key, subvalue] of Object.entries(record)) {
57
+ if (key.charAt(0) !== ".")
58
+ break;
59
+ analyzeThing(subvalue, `${path}["${key}"]`);
60
+ dots = true;
61
+ }
62
+ if (dots)
63
+ return;
64
+ let explicit = false;
65
+ const conditionImport = Boolean("import" in record && record.import);
66
+ const conditionRequire = Boolean("require" in record && record.require);
67
+ const conditionDefault = Boolean("default" in record && record.default);
68
+ if (conditionImport || conditionRequire) {
69
+ explicit = true;
70
+ }
71
+ if (conditionImport || conditionRequire && conditionDefault) {
72
+ esm = true;
73
+ }
74
+ if (conditionRequire || conditionImport && conditionDefault) {
75
+ cjs = true;
76
+ }
77
+ const defaults = record.node || record.default;
78
+ if (typeof defaults === "string" && !explicit) {
79
+ if (/\.mjs$/.test(defaults))
80
+ esm = true;
81
+ if (/\.cjs$/.test(defaults))
82
+ cjs = true;
83
+ }
84
+ }
85
+ } else if (typeof value === "string") {
86
+ if (/\.mjs$/.test(value))
87
+ esm = true;
88
+ if (/\.cjs$/.test(value))
89
+ cjs = true;
90
+ } else if (value === null) ; else {
91
+ console.error("unknown:", [value], path);
92
+ }
93
+ }
94
+ }
95
+
96
+ function stripBomTag(content) {
97
+ if (content.charCodeAt(0) === 65279) {
98
+ return content.slice(1);
99
+ }
100
+ return content;
101
+ }
102
+
103
+ async function analyzePackage(pkg) {
104
+ const _pkg = pkg;
105
+ if (_pkg.module)
106
+ return _pkg;
107
+ const content = await fs.readFile(join(pkg.path, "package.json"), "utf-8");
108
+ const json = JSON.parse(stripBomTag(content));
109
+ _pkg.module = analyzePackageJson(json);
110
+ return _pkg;
111
+ }
112
+
113
+ async function listPackageDependencies(options) {
114
+ const manager = await detect({
115
+ cwd: options.cwd
116
+ });
117
+ if (!manager)
118
+ throw new Error("Cannot detect package manager in the current patch");
119
+ if (manager.name === "pnpm")
120
+ return await import('./chunks/pnpm.mjs').then((r) => r.listPackageDependencies(options));
121
+ else
122
+ throw new Error(`Package manager ${manager.name} is not yet supported`);
123
+ }
124
+
125
+ export { analyzePackage, listPackageDependencies };
package/package.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "node-modules-tools",
3
+ "type": "module",
4
+ "version": "0.0.0",
5
+ "description": "Tools for inspecting node_modules",
6
+ "author": "Anthony Fu <anthonyfu117@hotmail.com>",
7
+ "license": "MIT",
8
+ "funding": "https://github.com/sponsors/antfu",
9
+ "homepage": "https://github.com/antfu/node-modules-inspector#readme",
10
+ "repository": {
11
+ "type": "git",
12
+ "url": "git+https://github.com/antfu/node-modules-inspector.git",
13
+ "directory": "packages/node-modules-tools"
14
+ },
15
+ "bugs": "https://github.com/antfu/node-modules-inspector/issues",
16
+ "keywords": [],
17
+ "sideEffects": false,
18
+ "exports": {
19
+ ".": "./dist/index.mjs"
20
+ },
21
+ "main": "./dist/index.mjs",
22
+ "module": "./dist/index.mjs",
23
+ "types": "./dist/index.d.mts",
24
+ "typesVersions": {
25
+ "*": {
26
+ "*": [
27
+ "./dist/*",
28
+ "./dist/index.d.ts"
29
+ ]
30
+ }
31
+ },
32
+ "files": [
33
+ "dist"
34
+ ],
35
+ "dependencies": {
36
+ "package-manager-detector": "^0.2.8",
37
+ "tinyexec": "^0.3.2"
38
+ },
39
+ "devDependencies": {
40
+ "@pnpm/list": "^1000.0.5",
41
+ "@pnpm/types": "^1000.1.0",
42
+ "pkg-types": "^1.3.1",
43
+ "unbuild": "^3.3.1"
44
+ },
45
+ "scripts": {
46
+ "build": "unbuild",
47
+ "dev": "unbuild --stub",
48
+ "start": "tsx src/index.ts",
49
+ "test": "vitest"
50
+ }
51
+ }