node-modules-tools 1.1.3 → 1.2.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.
@@ -0,0 +1,288 @@
1
+ import fs from 'node:fs';
2
+ import { join, relative } from 'pathe';
3
+ import { x } from 'tinyexec';
4
+ import { CLUSTER_DEP_PROD, CLUSTER_DEP_DEV, CLUSTER_DEP_OPTIONAL } from '../constants.mjs';
5
+
6
+ async function getBunVersion(options) {
7
+ try {
8
+ const raw = await x("bun", ["--version"], {
9
+ throwOnError: true,
10
+ nodeOptions: { cwd: options.cwd }
11
+ });
12
+ return raw.stdout.trim();
13
+ } catch (err) {
14
+ console.error("Failed to get bun version");
15
+ console.error(err);
16
+ return void 0;
17
+ }
18
+ }
19
+ async function readBunLockfile(root) {
20
+ const lockfilePath = join(root, "bun.lock");
21
+ const lockbPath = join(root, "bun.lockb");
22
+ if (fs.existsSync(lockfilePath)) {
23
+ try {
24
+ const content = await fs.promises.readFile(lockfilePath, "utf-8");
25
+ const cleanedContent = content.replace(/,(\s*[}\]])/g, "$1");
26
+ return JSON.parse(cleanedContent);
27
+ } catch (err) {
28
+ console.error("Failed to parse bun.lock");
29
+ console.error(err);
30
+ }
31
+ }
32
+ if (fs.existsSync(lockbPath)) {
33
+ throw new Error(
34
+ "Binary bun.lockb format is not supported. Please use the new bun.lock format. https://bun.sh/docs/install/lockfile"
35
+ );
36
+ }
37
+ return null;
38
+ }
39
+ function parseSpec(spec) {
40
+ const atIndex = spec.lastIndexOf("@");
41
+ if (atIndex <= 0)
42
+ return { name: spec, version: "0.0.0" };
43
+ return {
44
+ name: spec.slice(0, atIndex),
45
+ version: spec.slice(atIndex + 1) || "0.0.0"
46
+ };
47
+ }
48
+ function splitPackageKey(key) {
49
+ const result = [];
50
+ let index = 0;
51
+ while (index < key.length) {
52
+ if (key[index] === "@") {
53
+ const scopeEnd = key.indexOf("/", index + 1);
54
+ if (scopeEnd === -1) {
55
+ result.push(key.slice(index));
56
+ break;
57
+ }
58
+ const nameEnd = key.indexOf("/", scopeEnd + 1);
59
+ if (nameEnd === -1) {
60
+ result.push(key.slice(index));
61
+ break;
62
+ }
63
+ result.push(key.slice(index, nameEnd));
64
+ index = nameEnd + 1;
65
+ } else {
66
+ const nextSlash = key.indexOf("/", index);
67
+ if (nextSlash === -1) {
68
+ result.push(key.slice(index));
69
+ break;
70
+ }
71
+ result.push(key.slice(index, nextSlash));
72
+ index = nextSlash + 1;
73
+ }
74
+ }
75
+ return result;
76
+ }
77
+ function resolvePackageFilepath(root, key) {
78
+ const packages = splitPackageKey(key);
79
+ if (!packages.length)
80
+ return join(root, "node_modules");
81
+ const parts = [root];
82
+ for (const pkg of packages) {
83
+ parts.push("node_modules");
84
+ if (pkg.startsWith("@")) {
85
+ const slash = pkg.indexOf("/");
86
+ if (slash === -1) {
87
+ parts.push(pkg);
88
+ } else {
89
+ parts.push(pkg.slice(0, slash));
90
+ parts.push(pkg.slice(slash + 1));
91
+ }
92
+ } else {
93
+ parts.push(pkg);
94
+ }
95
+ }
96
+ return join(...parts);
97
+ }
98
+ function composeChildKey(parent, dependency) {
99
+ return parent ? `${parent}/${dependency}` : dependency;
100
+ }
101
+ function withCluster(clusters, cluster) {
102
+ const set = new Set(clusters);
103
+ set.add(cluster);
104
+ return set;
105
+ }
106
+ async function listPackageDependencies(options) {
107
+ const root = options.cwd;
108
+ const lockfile = await readBunLockfile(root);
109
+ if (!lockfile)
110
+ throw new Error("Could not find or parse bun.lock file");
111
+ const packages = /* @__PURE__ */ new Map();
112
+ const processedSpecs = /* @__PURE__ */ new Set();
113
+ const packageInfoByKey = /* @__PURE__ */ new Map();
114
+ const packageKeysByName = /* @__PURE__ */ new Map();
115
+ Object.entries(lockfile.packages ?? {}).forEach(([key, tuple]) => {
116
+ const [spec, , metadata] = tuple;
117
+ const { name, version } = parseSpec(spec);
118
+ const info = {
119
+ key,
120
+ spec,
121
+ name,
122
+ version,
123
+ metadata: metadata || {}
124
+ };
125
+ packageInfoByKey.set(key, info);
126
+ const existing = packageKeysByName.get(name);
127
+ if (existing)
128
+ existing.push(key);
129
+ else
130
+ packageKeysByName.set(name, [key]);
131
+ });
132
+ function resolveDependencyKey(parentKey, dependency) {
133
+ if (parentKey) {
134
+ const scopedKey = composeChildKey(parentKey, dependency);
135
+ if (packageInfoByKey.has(scopedKey))
136
+ return scopedKey;
137
+ }
138
+ const candidates = packageKeysByName.get(dependency);
139
+ if (candidates && candidates.length) {
140
+ if (parentKey) {
141
+ const nested = candidates.find((candidate) => candidate.startsWith(`${parentKey}/`));
142
+ if (nested)
143
+ return nested;
144
+ }
145
+ const direct = candidates.find((candidate) => candidate === dependency);
146
+ if (direct)
147
+ return direct;
148
+ return candidates[0];
149
+ }
150
+ if (!parentKey && packageInfoByKey.has(dependency))
151
+ return dependency;
152
+ return void 0;
153
+ }
154
+ function getOrCreateNode(info) {
155
+ let node = packages.get(info.spec);
156
+ if (node)
157
+ return node;
158
+ node = {
159
+ spec: info.spec,
160
+ name: info.name,
161
+ version: info.version,
162
+ filepath: resolvePackageFilepath(root, info.key),
163
+ dependencies: /* @__PURE__ */ new Set(),
164
+ workspace: false,
165
+ clusters: /* @__PURE__ */ new Set()
166
+ };
167
+ packages.set(node.spec, node);
168
+ return node;
169
+ }
170
+ function traverse(key, clusters) {
171
+ const info = packageInfoByKey.get(key);
172
+ if (!info)
173
+ return void 0;
174
+ const node = getOrCreateNode(info);
175
+ if (!node.workspace) {
176
+ for (const cluster of clusters)
177
+ node.clusters.add(cluster);
178
+ }
179
+ if (options.traverseFilter?.(node) === false)
180
+ return node;
181
+ if (processedSpecs.has(node.spec))
182
+ return node;
183
+ processedSpecs.add(node.spec);
184
+ if (options.dependenciesFilter?.(node) === false)
185
+ return node;
186
+ const directDependencies = Object.keys(info.metadata.dependencies ?? {});
187
+ for (const dependency of directDependencies) {
188
+ const childKey = resolveDependencyKey(key, dependency);
189
+ if (!childKey)
190
+ continue;
191
+ const childNode = traverse(childKey, clusters);
192
+ if (childNode)
193
+ node.dependencies.add(childNode.spec);
194
+ }
195
+ const optionalDependencies = Object.keys(info.metadata.optionalDependencies ?? {});
196
+ if (optionalDependencies.length) {
197
+ const clusterWithOptional = withCluster(clusters, CLUSTER_DEP_OPTIONAL);
198
+ for (const dependency of optionalDependencies) {
199
+ const childKey = resolveDependencyKey(key, dependency);
200
+ if (!childKey)
201
+ continue;
202
+ const childNode = traverse(childKey, clusterWithOptional);
203
+ if (childNode)
204
+ node.dependencies.add(childNode.spec);
205
+ }
206
+ }
207
+ return node;
208
+ }
209
+ const workspaceEntries = Object.entries(lockfile.workspaces ?? {});
210
+ if (!workspaceEntries.length)
211
+ workspaceEntries.push(["", {}]);
212
+ const workspaceNodeByName = /* @__PURE__ */ new Map();
213
+ const workspacePackages = await Promise.all(workspaceEntries.map(async ([workspacePath, workspace], index) => {
214
+ const workspaceData = workspace ?? {};
215
+ let name = workspaceData.name;
216
+ const workspaceRoot = join(root, workspacePath);
217
+ if (!name) {
218
+ let path = relative(root, workspaceRoot);
219
+ if (path === ".")
220
+ path = "";
221
+ const suffix = path.toLowerCase().replace(/[^a-z0-9-]+/g, "_").slice(0, 20);
222
+ name = suffix ? `#workspace-${suffix}` : index === 0 ? "#workspace-root" : `#workspace-${index + 1}`;
223
+ }
224
+ const packageJsonPath = join(workspaceRoot, "package.json");
225
+ let version = "0.0.0";
226
+ let isPrivate = false;
227
+ if (fs.existsSync(packageJsonPath)) {
228
+ try {
229
+ const raw = await fs.promises.readFile(packageJsonPath, "utf-8");
230
+ const pkg = JSON.parse(raw);
231
+ if (!workspaceData.name && pkg.name)
232
+ name = pkg.name;
233
+ if (pkg.version)
234
+ version = pkg.version;
235
+ if (pkg.private)
236
+ isPrivate = true;
237
+ } catch {
238
+ }
239
+ }
240
+ const spec = `${name}@${version}`;
241
+ const node = {
242
+ spec,
243
+ name,
244
+ version,
245
+ filepath: workspaceRoot,
246
+ dependencies: /* @__PURE__ */ new Set(),
247
+ workspace: true,
248
+ clusters: /* @__PURE__ */ new Set()
249
+ };
250
+ if (isPrivate)
251
+ node.private = true;
252
+ packages.set(spec, node);
253
+ workspaceNodeByName.set(name, node);
254
+ return { workspace: workspaceData, node };
255
+ }));
256
+ for (const { workspace, node } of workspacePackages) {
257
+ const depGroups = [
258
+ { entries: workspace.dependencies, clusters: [CLUSTER_DEP_PROD] },
259
+ { entries: workspace.devDependencies, clusters: [CLUSTER_DEP_DEV] },
260
+ { entries: workspace.optionalDependencies, clusters: [CLUSTER_DEP_OPTIONAL] }
261
+ ];
262
+ for (const { entries, clusters } of depGroups) {
263
+ if (!entries)
264
+ continue;
265
+ for (const dependency of Object.keys(entries)) {
266
+ const workspaceNode = workspaceNodeByName.get(dependency);
267
+ if (workspaceNode) {
268
+ node.dependencies.add(workspaceNode.spec);
269
+ continue;
270
+ }
271
+ const childKey = resolveDependencyKey(null, dependency);
272
+ if (!childKey)
273
+ continue;
274
+ const childNode = traverse(childKey, clusters);
275
+ if (childNode)
276
+ node.dependencies.add(childNode.spec);
277
+ }
278
+ }
279
+ }
280
+ return {
281
+ root,
282
+ packageManager: "bun",
283
+ packageManagerVersion: await getBunVersion(options),
284
+ packages
285
+ };
286
+ }
287
+
288
+ export { listPackageDependencies };
@@ -1,3 +1,3 @@
1
- export { d as CLUSTER_DEP_DEV, e as CLUSTER_DEP_OPTIONAL, C as CLUSTER_DEP_PROD, c as PackageModuleTypes } from './shared/node-modules-tools.Dnbbfhc0.mjs';
1
+ export { d as CLUSTER_DEP_DEV, e as CLUSTER_DEP_OPTIONAL, C as CLUSTER_DEP_PROD, c as PackageModuleTypes } from './shared/node-modules-tools.D94dzcLg.mjs';
2
2
  import 'pkg-types';
3
3
  import 'publint';
@@ -1,3 +1,3 @@
1
- export { d as CLUSTER_DEP_DEV, e as CLUSTER_DEP_OPTIONAL, C as CLUSTER_DEP_PROD, c as PackageModuleTypes } from './shared/node-modules-tools.Dnbbfhc0.js';
1
+ export { d as CLUSTER_DEP_DEV, e as CLUSTER_DEP_OPTIONAL, C as CLUSTER_DEP_PROD, c as PackageModuleTypes } from './shared/node-modules-tools.D94dzcLg.js';
2
2
  import 'pkg-types';
3
3
  import 'publint';
package/dist/index.d.mts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { AgentName } from 'package-manager-detector';
2
- import { P as PackageNodeRaw, a as PackageNodeBase, b as PackageNode } from './shared/node-modules-tools.Dnbbfhc0.mjs';
3
- export { d as CLUSTER_DEP_DEV, e as CLUSTER_DEP_OPTIONAL, C as CLUSTER_DEP_PROD, F as FileCategory, N as NpmMeta, h as NpmMetaLatest, i as PackageInstallSizeInfo, g as PackageModuleType, f as PackageModuleTypeSimple, c as PackageModuleTypes } from './shared/node-modules-tools.Dnbbfhc0.mjs';
2
+ import { P as PackageNodeRaw, a as PackageNodeBase, b as PackageNode } from './shared/node-modules-tools.D94dzcLg.mjs';
3
+ export { d as CLUSTER_DEP_DEV, e as CLUSTER_DEP_OPTIONAL, C as CLUSTER_DEP_PROD, F as FileCategory, N as NpmMeta, h as NpmMetaLatest, i as PackageInstallSizeInfo, g as PackageModuleType, f as PackageModuleTypeSimple, c as PackageModuleTypes } from './shared/node-modules-tools.D94dzcLg.mjs';
4
4
  export { PackageJson } from 'pkg-types';
5
5
  export { Message as PublintMessage } from 'publint';
6
6
  export { NormalizedFunding, PackageNodeLike, ParsedFunding, ParsedLicense, constructPackageFilter, constructPackageFilters, normalizePkgAuthors, normalizePkgFundings, normalizePkgLicense, normalizePkgRepository, parseAuthor, parseFunding } from './utils.mjs';
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { AgentName } from 'package-manager-detector';
2
- import { P as PackageNodeRaw, a as PackageNodeBase, b as PackageNode } from './shared/node-modules-tools.Dnbbfhc0.js';
3
- export { d as CLUSTER_DEP_DEV, e as CLUSTER_DEP_OPTIONAL, C as CLUSTER_DEP_PROD, F as FileCategory, N as NpmMeta, h as NpmMetaLatest, i as PackageInstallSizeInfo, g as PackageModuleType, f as PackageModuleTypeSimple, c as PackageModuleTypes } from './shared/node-modules-tools.Dnbbfhc0.js';
2
+ import { P as PackageNodeRaw, a as PackageNodeBase, b as PackageNode } from './shared/node-modules-tools.D94dzcLg.js';
3
+ export { d as CLUSTER_DEP_DEV, e as CLUSTER_DEP_OPTIONAL, C as CLUSTER_DEP_PROD, F as FileCategory, N as NpmMeta, h as NpmMetaLatest, i as PackageInstallSizeInfo, g as PackageModuleType, f as PackageModuleTypeSimple, c as PackageModuleTypes } from './shared/node-modules-tools.D94dzcLg.js';
4
4
  export { PackageJson } from 'pkg-types';
5
5
  export { Message as PublintMessage } from 'publint';
6
6
  export { NormalizedFunding, PackageNodeLike, ParsedFunding, ParsedLicense, constructPackageFilter, constructPackageFilters, normalizePkgAuthors, normalizePkgFundings, normalizePkgLicense, normalizePkgRepository, parseAuthor, parseFunding } from './utils.js';
package/dist/index.mjs CHANGED
@@ -24,6 +24,8 @@ async function listPackageDependenciesRaw(manager, options) {
24
24
  result = await import('./chunks/index.mjs').then((r) => r.listPackageDependencies(options));
25
25
  else if (manager === "npm")
26
26
  result = await import('./chunks/index2.mjs').then((r) => r.listPackageDependencies(options));
27
+ else if (manager === "bun")
28
+ result = await import('./chunks/index3.mjs').then((r) => r.listPackageDependencies(options));
27
29
  else
28
30
  throw new Error(`Package manager ${manager} is not yet supported`);
29
31
  return populateRawResult(result);
@@ -80,8 +80,8 @@ interface NpmMetaLatest extends NpmMeta {
80
80
  */
81
81
  fetechedAt: number;
82
82
  /**
83
- * We calculate a smart "TTL" based on how open the package updates.
84
- * If this timestemp is greater than the current time, the meta should be discarded.
83
+ * We calculate a smart "TTL" based on how often the package updates.
84
+ * If this timestamp is greater than the current time, the meta should be discarded.
85
85
  */
86
86
  vaildUntil: number;
87
87
  }
@@ -80,8 +80,8 @@ interface NpmMetaLatest extends NpmMeta {
80
80
  */
81
81
  fetechedAt: number;
82
82
  /**
83
- * We calculate a smart "TTL" based on how open the package updates.
84
- * If this timestemp is greater than the current time, the meta should be discarded.
83
+ * We calculate a smart "TTL" based on how often the package updates.
84
+ * If this timestamp is greater than the current time, the meta should be discarded.
85
85
  */
86
86
  vaildUntil: number;
87
87
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "node-modules-tools",
3
3
  "type": "module",
4
- "version": "1.1.3",
4
+ "version": "1.2.0",
5
5
  "description": "Tools for inspecting node_modules",
6
6
  "author": "Anthony Fu <anthonyfu117@hotmail.com>",
7
7
  "license": "MIT",
@@ -29,16 +29,16 @@
29
29
  "dependencies": {
30
30
  "js-yaml": "^4.1.0",
31
31
  "p-limit": "^6.2.0",
32
- "package-manager-detector": "^1.3.0",
32
+ "package-manager-detector": "^1.5.0",
33
33
  "pathe": "^2.0.3",
34
34
  "pkg-types": "^2.3.0",
35
- "publint": "^0.3.13",
36
- "semver": "^7.7.2",
35
+ "publint": "^0.3.15",
36
+ "semver": "^7.7.3",
37
37
  "tinyexec": "^1.0.1"
38
38
  },
39
39
  "devDependencies": {
40
- "@pnpm/list": "^1000.1.1",
41
- "@pnpm/types": "^1000.8.0",
40
+ "@pnpm/list": "^1000.1.3",
41
+ "@pnpm/types": "^1000.9.0",
42
42
  "@types/js-yaml": "^4.0.9",
43
43
  "@types/stream-json": "^1.7.8",
44
44
  "stream-json": "^1.9.1",