@reliverse/dler 2.3.2 → 2.3.4

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,415 @@
1
+ import { existsSync, readdirSync, statSync } from "node:fs";
2
+ import { relative, resolve } from "node:path";
3
+ import { createIncludeFilter } from "@reliverse/matcha";
4
+ import path from "@reliverse/pathkit";
5
+ import { logger } from "@reliverse/relinka";
6
+ import { hasWorkspaces, readPackageJSON } from "@reliverse/typerso";
7
+ const monorepoCache = /* @__PURE__ */ new Map();
8
+ async function detectMonorepo(startDir) {
9
+ const cwd = resolve(startDir ?? process.cwd());
10
+ if (monorepoCache.has(cwd)) {
11
+ return monorepoCache.get(cwd);
12
+ }
13
+ let currentDir = cwd;
14
+ while (true) {
15
+ const packageJsonPath2 = path.join(currentDir, "package.json");
16
+ if (existsSync(packageJsonPath2)) {
17
+ const packageJson2 = await readPackageJSON(currentDir);
18
+ if (packageJson2 && hasWorkspaces(packageJson2)) {
19
+ const workspacePackages = await discoverWorkspacePackages(currentDir, packageJson2);
20
+ const result2 = {
21
+ isMonorepo: true,
22
+ rootPath: currentDir,
23
+ rootPackageJson: packageJson2,
24
+ workspacePackages
25
+ };
26
+ monorepoCache.set(cwd, result2);
27
+ return result2;
28
+ }
29
+ }
30
+ const parentDir = path.dirname(currentDir);
31
+ if (parentDir === currentDir) {
32
+ break;
33
+ }
34
+ currentDir = parentDir;
35
+ }
36
+ const packageJsonPath = path.join(cwd, "package.json");
37
+ if (!existsSync(packageJsonPath)) {
38
+ throw new Error("No package.json found in current directory or any parent directory");
39
+ }
40
+ const packageJson = await readPackageJSON(cwd);
41
+ if (!packageJson) {
42
+ throw new Error("Could not read package.json");
43
+ }
44
+ const result = {
45
+ isMonorepo: false,
46
+ rootPath: cwd,
47
+ rootPackageJson: packageJson
48
+ };
49
+ monorepoCache.set(cwd, result);
50
+ return result;
51
+ }
52
+ const workspaceCache = /* @__PURE__ */ new Map();
53
+ function hasBiomeConfig(rootPath) {
54
+ return existsSync(path.join(rootPath, "biome.json")) || existsSync(path.join(rootPath, "biome.jsonc"));
55
+ }
56
+ function hasTurboConfig(rootPath) {
57
+ return existsSync(path.join(rootPath, "turbo.json"));
58
+ }
59
+ async function discoverWorkspacePackages(monorepoRoot, rootPackageJson) {
60
+ if (workspaceCache.has(monorepoRoot)) {
61
+ return workspaceCache.get(monorepoRoot);
62
+ }
63
+ const packages = [];
64
+ if (!rootPackageJson.workspaces?.packages) {
65
+ workspaceCache.set(monorepoRoot, packages);
66
+ return packages;
67
+ }
68
+ const patterns = Array.isArray(rootPackageJson.workspaces.packages) ? rootPackageJson.workspaces.packages : [];
69
+ const packagePaths = [];
70
+ const validationPromises = [];
71
+ for (const pattern of patterns) {
72
+ if (pattern.includes("*")) {
73
+ const glob = new Bun.Glob(pattern);
74
+ const matches = glob.scanSync({ cwd: monorepoRoot, onlyFiles: false });
75
+ for (const match of matches) {
76
+ const packagePath = resolve(monorepoRoot, match);
77
+ packagePaths.push(packagePath);
78
+ validationPromises.push(isValidWorkspacePackage(packagePath));
79
+ }
80
+ } else {
81
+ const packagePath = resolve(monorepoRoot, pattern);
82
+ packagePaths.push(packagePath);
83
+ validationPromises.push(isValidWorkspacePackage(packagePath));
84
+ }
85
+ }
86
+ const validationResults = await Promise.all(validationPromises);
87
+ for (let i = 0; i < packagePaths.length; i++) {
88
+ if (validationResults[i]) {
89
+ packages.push(packagePaths[i]);
90
+ }
91
+ }
92
+ workspaceCache.set(monorepoRoot, packages);
93
+ return packages;
94
+ }
95
+ async function isValidWorkspacePackage(packagePath) {
96
+ const packageJsonPath = path.join(packagePath, "package.json");
97
+ if (!existsSync(packageJsonPath)) {
98
+ return false;
99
+ }
100
+ const packageJson = await readPackageJSON(packagePath);
101
+ return !!packageJson?.name;
102
+ }
103
+ function containsGlobPattern(str) {
104
+ return str.includes("*") || str.includes("?") || str.includes("[") || str.includes("{");
105
+ }
106
+ function containsMultipleTargets(str) {
107
+ return str.includes(" ") && str.trim().split(/\s+/).length > 1;
108
+ }
109
+ const packageInfoCache = /* @__PURE__ */ new Map();
110
+ async function getPackageInfo(monorepoInfo) {
111
+ if (packageInfoCache.has(monorepoInfo.rootPath)) {
112
+ return packageInfoCache.get(monorepoInfo.rootPath);
113
+ }
114
+ const packages = [];
115
+ if (monorepoInfo.isMonorepo) {
116
+ if (hasDependencies(monorepoInfo.rootPackageJson)) {
117
+ packages.push({
118
+ name: monorepoInfo.rootPackageJson.name || "root",
119
+ path: monorepoInfo.rootPath,
120
+ json: monorepoInfo.rootPackageJson,
121
+ packagePath: monorepoInfo.rootPath,
122
+ packageJson: monorepoInfo.rootPackageJson
123
+ });
124
+ }
125
+ if (monorepoInfo.workspacePackages) {
126
+ for (const packagePath of monorepoInfo.workspacePackages) {
127
+ const packageJson = await readPackageJSON(packagePath);
128
+ if (packageJson && hasDependencies(packageJson)) {
129
+ packages.push({
130
+ name: packageJson.name || "unnamed",
131
+ path: packagePath,
132
+ json: packageJson,
133
+ packagePath,
134
+ packageJson
135
+ });
136
+ }
137
+ }
138
+ }
139
+ } else {
140
+ packages.push({
141
+ name: monorepoInfo.rootPackageJson.name || "project",
142
+ path: monorepoInfo.rootPath,
143
+ json: monorepoInfo.rootPackageJson,
144
+ packagePath: monorepoInfo.rootPath,
145
+ packageJson: monorepoInfo.rootPackageJson
146
+ });
147
+ }
148
+ packageInfoCache.set(monorepoInfo.rootPath, packages);
149
+ return packages;
150
+ }
151
+ function hasDependencies(packageJson) {
152
+ return !!(packageJson.dependencies || packageJson.devDependencies || packageJson.peerDependencies || packageJson.optionalDependencies);
153
+ }
154
+ async function resolveTargetPackages(monorepoInfo, targetOption, w) {
155
+ const allPackages = await getPackageInfo(monorepoInfo);
156
+ if (!(targetOption || w)) {
157
+ const currentDir = process.cwd();
158
+ const currentPackage = allPackages.find((pkg) => pkg.path === currentDir);
159
+ if (currentPackage) {
160
+ return [currentPackage];
161
+ }
162
+ return allPackages;
163
+ }
164
+ if (w) {
165
+ const rootPackage = allPackages.find((pkg) => pkg.path === monorepoInfo.rootPath);
166
+ return rootPackage ? [rootPackage] : [];
167
+ }
168
+ if (targetOption) {
169
+ if (containsGlobPattern(targetOption) || containsMultipleTargets(targetOption)) {
170
+ const targets = [];
171
+ if (containsMultipleTargets(targetOption)) {
172
+ targets.push(...targetOption.trim().split(/\s+/));
173
+ } else {
174
+ targets.push(targetOption);
175
+ }
176
+ const matchedPackages = [];
177
+ const filter = createIncludeFilter(targets);
178
+ const matchingPackages = filter(allPackages);
179
+ matchedPackages.push(...matchingPackages);
180
+ return matchedPackages;
181
+ } else {
182
+ const pkg = allPackages.find(
183
+ (p) => p.name === targetOption || relative(monorepoInfo.rootPath, p.path) === targetOption || p.path === resolve(monorepoInfo.rootPath, targetOption)
184
+ );
185
+ return pkg ? [pkg] : [];
186
+ }
187
+ }
188
+ return [];
189
+ }
190
+ function getSourceFiles(packagePath, rootPath) {
191
+ const files = [];
192
+ const extensions = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"];
193
+ const skipDirs = ["node_modules", ".git", "dist", "build", ".cache", "coverage", ".turbo"];
194
+ function scanDir(dir) {
195
+ try {
196
+ const entries = readdirSync(dir);
197
+ for (const entry of entries) {
198
+ const fullPath = path.join(dir, entry);
199
+ const stat = statSync(fullPath);
200
+ if (stat.isDirectory()) {
201
+ if (!skipDirs.includes(entry)) {
202
+ scanDir(fullPath);
203
+ }
204
+ } else if (stat.isFile()) {
205
+ const ext = path.extname(entry);
206
+ if (extensions.includes(ext)) {
207
+ files.push(fullPath);
208
+ }
209
+ }
210
+ }
211
+ } catch (error) {
212
+ }
213
+ }
214
+ scanDir(packagePath);
215
+ return files;
216
+ }
217
+ function analyzePackageScripts(packageJson) {
218
+ const usedPackages = /* @__PURE__ */ new Set();
219
+ if (packageJson.scripts) {
220
+ for (const script of Object.values(packageJson.scripts)) {
221
+ const buildTools = ["biome", "turbo", "ultracite"];
222
+ for (const tool of buildTools) {
223
+ if (script.includes(tool)) {
224
+ if (tool === "biome") {
225
+ usedPackages.add("@biomejs/biome");
226
+ } else {
227
+ usedPackages.add(tool);
228
+ }
229
+ }
230
+ }
231
+ }
232
+ }
233
+ return usedPackages;
234
+ }
235
+ function analyzeSourceFile(filePath, packageJson) {
236
+ const usedPackages = /* @__PURE__ */ new Set();
237
+ const packageName = packageJson.name || "";
238
+ try {
239
+ const content = require("node:fs").readFileSync(filePath, "utf8");
240
+ const importPatterns = [
241
+ // ES6 imports
242
+ /import\s+.*?\s+from\s+['"]([^'"]+)['"]/g,
243
+ // Dynamic imports
244
+ /import\s*\(\s*['"]([^'"]+)['"]\s*\)/g,
245
+ // Require statements
246
+ /require\s*\(\s*['"]([^'"]+)['"]\s*\)/g,
247
+ // TypeScript type imports
248
+ /import\s*\(\s*['"]([^'"]+)['"]\s*\)/g
249
+ ];
250
+ for (const pattern of importPatterns) {
251
+ let match = pattern.exec(content);
252
+ while (match !== null) {
253
+ const importedPackage = match[1];
254
+ if (!importedPackage) continue;
255
+ if (!(importedPackage.startsWith(".") || path.extname(importedPackage))) {
256
+ const basePackage = importedPackage.split("/")[0];
257
+ if (basePackage && basePackage.startsWith("@")) {
258
+ const parts = importedPackage.split("/");
259
+ if (parts.length >= 2) {
260
+ usedPackages.add(`${parts[0]}/${parts[1]}`);
261
+ }
262
+ } else if (basePackage) {
263
+ usedPackages.add(basePackage);
264
+ }
265
+ }
266
+ match = pattern.exec(content);
267
+ }
268
+ }
269
+ if (content.includes(packageName)) {
270
+ usedPackages.delete(packageName);
271
+ }
272
+ } catch (error) {
273
+ }
274
+ return usedPackages;
275
+ }
276
+ function collectAllDependencies(packageJson, scope, includePeer = false) {
277
+ const allDeps = {};
278
+ const depTypes = [
279
+ { key: "dependencies", type: "prod" },
280
+ { key: "devDependencies", type: "dev" },
281
+ { key: "peerDependencies", type: "peer" },
282
+ { key: "optionalDependencies", type: "optional" }
283
+ ];
284
+ for (const { key, type } of depTypes) {
285
+ if (packageJson[key] && (!scope || scope === type)) {
286
+ if (type === "peer" && !includePeer) {
287
+ continue;
288
+ }
289
+ Object.assign(allDeps, packageJson[key]);
290
+ }
291
+ }
292
+ return allDeps;
293
+ }
294
+ export async function findUnusedDependencies(options) {
295
+ const monorepoInfo = await detectMonorepo(options.cwd);
296
+ const targetPackages = await resolveTargetPackages(monorepoInfo, options.target, options.w);
297
+ if (targetPackages.length === 0) {
298
+ logger.warn("No packages found to analyze");
299
+ return;
300
+ }
301
+ const hasBiome = hasBiomeConfig(monorepoInfo.rootPath);
302
+ const hasTurbo = hasTurboConfig(monorepoInfo.rootPath);
303
+ let totalUnused = 0;
304
+ const results = [];
305
+ for (const targetPackage of targetPackages) {
306
+ if (options.verbose) {
307
+ logger.log(`Analyzing ${targetPackage.packageJson.name || "unnamed package"}...`);
308
+ }
309
+ const sourceFiles = getSourceFiles(targetPackage.packagePath, monorepoInfo.rootPath);
310
+ const usedPackages = /* @__PURE__ */ new Set();
311
+ for (const file of sourceFiles) {
312
+ const fileUsedPackages = analyzeSourceFile(file, targetPackage.packageJson);
313
+ for (const pkg of fileUsedPackages) {
314
+ usedPackages.add(pkg);
315
+ }
316
+ }
317
+ const scriptUsedPackages = analyzePackageScripts(targetPackage.packageJson);
318
+ for (const pkg of scriptUsedPackages) {
319
+ usedPackages.add(pkg);
320
+ }
321
+ if (hasBiome) {
322
+ usedPackages.add("@biomejs/biome");
323
+ usedPackages.add("ultracite");
324
+ }
325
+ if (hasTurbo) {
326
+ usedPackages.add("turbo");
327
+ }
328
+ const allDeps = collectAllDependencies(
329
+ targetPackage.packageJson,
330
+ options.scope,
331
+ options.includePeer
332
+ );
333
+ const unusedDeps = [];
334
+ for (const [depName, depVersion] of Object.entries(allDeps)) {
335
+ if (options.ignore?.includes(depName)) {
336
+ continue;
337
+ }
338
+ if (!usedPackages.has(depName)) {
339
+ let scope = "unknown";
340
+ if (targetPackage.packageJson.dependencies?.[depName]) {
341
+ scope = "prod";
342
+ } else if (targetPackage.packageJson.devDependencies?.[depName]) {
343
+ scope = "dev";
344
+ } else if (targetPackage.packageJson.peerDependencies?.[depName]) {
345
+ scope = "peer";
346
+ } else if (targetPackage.packageJson.optionalDependencies?.[depName]) {
347
+ scope = "optional";
348
+ }
349
+ unusedDeps.push({
350
+ name: depName,
351
+ scope,
352
+ version: depVersion
353
+ });
354
+ }
355
+ }
356
+ if (unusedDeps.length > 0) {
357
+ results.push({
358
+ packageName: targetPackage.packageJson.name || "unnamed package",
359
+ packagePath: targetPackage.packagePath,
360
+ unusedDeps
361
+ });
362
+ totalUnused += unusedDeps.length;
363
+ }
364
+ }
365
+ if (results.length === 0) {
366
+ logger.success("\u2705 No unused dependencies found!");
367
+ return;
368
+ }
369
+ logger.log(`
370
+ \u{1F4E6} Found ${totalUnused} unused dependencies across ${results.length} packages:
371
+ `);
372
+ const packageSuggestions = [];
373
+ for (const result of results) {
374
+ const relativePath = relative(process.cwd(), result.packagePath);
375
+ const depsByScope = {};
376
+ const allDeps = [];
377
+ for (const dep of result.unusedDeps) {
378
+ if (!depsByScope[dep.scope]) {
379
+ depsByScope[dep.scope] = [];
380
+ }
381
+ depsByScope[dep.scope].push(dep.name);
382
+ allDeps.push(dep);
383
+ }
384
+ packageSuggestions.push({
385
+ packageName: result.packageName,
386
+ packagePath: result.packagePath,
387
+ relativePath,
388
+ depsByScope,
389
+ allDeps
390
+ });
391
+ }
392
+ for (const suggestion of packageSuggestions) {
393
+ logger.log(`\u{1F4C1} ${suggestion.packageName} (${suggestion.relativePath}):`);
394
+ for (const dep of suggestion.allDeps) {
395
+ logger.log(` \u274C ${dep.name}@${dep.version} (${dep.scope})`);
396
+ }
397
+ logger.log("");
398
+ }
399
+ logger.log("\u{1F527} Suggested removal commands:");
400
+ for (const suggestion of packageSuggestions) {
401
+ const packageTarget = suggestion.relativePath === "package.json" ? "." : suggestion.relativePath;
402
+ for (const [scope, deps] of Object.entries(suggestion.depsByScope)) {
403
+ if (deps.length > 0) {
404
+ const scopeFlag = scope === "prod" ? "" : ` --${scope}`;
405
+ const depList = deps.join(" ");
406
+ logger.log(` bun dler rm ${depList} --target "${packageTarget}"${scopeFlag}`);
407
+ }
408
+ }
409
+ logger.log("");
410
+ }
411
+ logger.log(`\u{1F4A1} Use --ignore flag to exclude specific packages from analysis`);
412
+ logger.log(
413
+ `\u{1F4A1} If some dep is used, but was marked as unused, please create an issue: https://github.com/reliverse/dler/issues`
414
+ );
415
+ }
@@ -1,12 +1,2 @@
1
- declare const _default: import("@reliverse/rempts-core").Command<{
2
- ci: import("@reliverse/rempts-core").CLIOption<import("arktype").BaseType<boolean | undefined, {}>>;
3
- cwd: import("@reliverse/rempts-core").CLIOption<import("arktype").BaseType<string | undefined, {}>>;
4
- name: import("@reliverse/rempts-core").CLIOption<import("arktype").BaseType<string | undefined, {}>>;
5
- ignore: import("@reliverse/rempts-core").CLIOption<import("arktype").BaseType<string | undefined, {}>>;
6
- dryRun: import("@reliverse/rempts-core").CLIOption<import("arktype").BaseType<boolean | undefined, {}>>;
7
- install: import("@reliverse/rempts-core").CLIOption<import("arktype").BaseType<boolean | undefined, {}>>;
8
- allowMajor: import("@reliverse/rempts-core").CLIOption<import("arktype").BaseType<boolean | undefined, {}>>;
9
- details: import("@reliverse/rempts-core").CLIOption<import("arktype").BaseType<boolean | undefined, {}>>;
10
- ignoreFields: import("@reliverse/rempts-core").CLIOption<import("arktype").BaseType<string | undefined, {}>>;
11
- }, {}, string>;
1
+ declare const _default: any;
12
2
  export default _default;
@@ -1,16 +1,21 @@
1
1
  import path from "@reliverse/pathkit";
2
+ import fs from "@reliverse/relifso";
2
3
  import { logger } from "@reliverse/relinka";
3
4
  import { defineCommand, option } from "@reliverse/rempts-core";
4
5
  import { type } from "arktype";
5
6
  import { msgs } from "../../const.js";
6
7
  import {
7
- checkPackageUpdatesForFile,
8
+ checkPackageUpdatesForAllFiles,
8
9
  handleInstallation,
9
10
  prepareAllUpdateCandidates,
10
11
  updatePackageJsonFileDirectly,
11
12
  validatePackageJson
12
13
  } from "./impl.js";
13
- import { displayStructuredUpdateResults } from "./utils.js";
14
+ import {
15
+ displayStructuredUpdateResults,
16
+ initializeCache,
17
+ prepareDependenciesForUpdate
18
+ } from "./utils.js";
14
19
  export default defineCommand({
15
20
  description: "Update all dependencies to their latest versions across all package.json files. Supports selective updates with glob patterns and comprehensive filtering options.",
16
21
  options: {
@@ -27,11 +32,13 @@ export default defineCommand({
27
32
  description: "Dependencies to exclude from updates, supports glob patterns (e.g. 'eslint-*', '@types/*')"
28
33
  }),
29
34
  dryRun: option(type("boolean | undefined"), {
35
+ short: "n",
30
36
  description: "Preview updates without making changes"
31
37
  }),
32
38
  install: option(type("boolean | undefined"), {
33
39
  description: "Run install after updating (default: true)",
34
- short: "i"
40
+ short: "i",
41
+ default: true
35
42
  }),
36
43
  allowMajor: option(type("boolean | undefined"), {
37
44
  description: "Allow major version updates (default: true)"
@@ -42,6 +49,10 @@ export default defineCommand({
42
49
  }),
43
50
  ignoreFields: option(type("string | undefined"), {
44
51
  description: "Dependency fields to ignore (e.g., 'peerDependencies,catalog')"
52
+ }),
53
+ verbose: option(type("boolean | undefined"), {
54
+ description: "Verbose output (shows install command output)",
55
+ short: "v"
45
56
  })
46
57
  },
47
58
  handler: async ({ flags }) => {
@@ -51,48 +62,91 @@ export default defineCommand({
51
62
  process.exit(1);
52
63
  }
53
64
  const ci = flags.ci ?? (!process.stdout.isTTY || !!process.env.CI);
65
+ const cwd = flags.cwd;
54
66
  const dryRun = flags.dryRun ?? false;
55
- const install = flags.install ?? true;
56
- const allowMajor = flags.allowMajor ?? true;
67
+ const install = flags.install;
68
+ const allowMajor = true;
57
69
  const details = flags.details ?? false;
70
+ const verbose = flags.verbose ?? false;
58
71
  const isDryRun = dryRun;
59
72
  const shouldInstall = install;
60
73
  const showDetails = details;
74
+ const isVerbose = verbose;
61
75
  const fieldsToIgnore = flags.ignoreFields ? typeof flags.ignoreFields === "string" ? flags.ignoreFields.split(",").map((s) => s.trim()) : [] : [];
62
76
  await validatePackageJson();
63
- const { packageJsonFiles, fileDepsMap } = await prepareAllUpdateCandidates();
77
+ await initializeCache(isVerbose);
78
+ let effectiveCwd = cwd;
79
+ if (!effectiveCwd) {
80
+ const currentDir = process.cwd();
81
+ const hasLocalPackageJson = await fs.pathExists(path.join(currentDir, "package.json"));
82
+ if (hasLocalPackageJson) {
83
+ effectiveCwd = ".";
84
+ }
85
+ }
86
+ const { packageJsonFiles, fileDepsMap } = await prepareAllUpdateCandidates(effectiveCwd);
64
87
  if (packageJsonFiles.length === 0) {
65
88
  logger.log("No package.json files found");
66
89
  return;
67
90
  }
68
- let totalUpdated = 0;
69
- const allResults = [];
91
+ const allUpdateResults = [];
92
+ const updateArgs = {
93
+ ci,
94
+ ...flags.name && {
95
+ name: typeof flags.name === "string" ? [flags.name] : flags.name
96
+ },
97
+ ...flags.ignore && {
98
+ ignore: typeof flags.ignore === "string" ? [flags.ignore] : flags.ignore
99
+ },
100
+ allowMajor,
101
+ dryRun: isDryRun,
102
+ install: shouldInstall,
103
+ ignoreFields: fieldsToIgnore,
104
+ concurrency: 50
105
+ };
70
106
  for (const packageJsonPath of packageJsonFiles) {
71
107
  const fileDeps = fileDepsMap.get(packageJsonPath);
72
- if (!fileDeps) {
73
- continue;
108
+ if (!fileDeps) continue;
109
+ const filteredDeps = prepareDependenciesForUpdate(fileDeps, {
110
+ name: updateArgs.name,
111
+ ignore: updateArgs.ignore,
112
+ ignoreFields: updateArgs.ignoreFields
113
+ });
114
+ if (filteredDeps.length === 0) continue;
115
+ const fileDepsMapForCheck = /* @__PURE__ */ new Map();
116
+ for (const depName of filteredDeps) {
117
+ const depInfo = fileDeps[depName];
118
+ if (depInfo) {
119
+ fileDepsMapForCheck.set(depName, {
120
+ versionSpec: depInfo.versionSpec,
121
+ locations: new Set(depInfo.locations),
122
+ files: /* @__PURE__ */ new Set([packageJsonPath])
123
+ });
124
+ }
74
125
  }
75
- const updateArgs = {
76
- ci,
77
- ...flags.name && {
78
- name: typeof flags.name === "string" ? [flags.name] : flags.name
79
- },
80
- ...flags.ignore && {
81
- ignore: typeof flags.ignore === "string" ? [flags.ignore] : flags.ignore
82
- },
83
- allowMajor,
84
- dryRun: isDryRun,
85
- install: shouldInstall,
86
- ignoreFields: fieldsToIgnore
87
- };
88
- const results = await checkPackageUpdatesForFile(fileDeps, updateArgs);
89
- allResults.push(...results);
90
- const toUpdate = results.filter((r) => r.updated && !r.error);
126
+ const filteredUpdateArgs = { ...updateArgs };
127
+ filteredUpdateArgs.name = void 0;
128
+ filteredUpdateArgs.ignore = void 0;
129
+ const fileResults2 = await checkPackageUpdatesForAllFiles(
130
+ fileDepsMapForCheck,
131
+ filteredUpdateArgs
132
+ );
133
+ allUpdateResults.push(...fileResults2);
134
+ }
135
+ const fileUpdatePromises = packageJsonFiles.map(async (packageJsonPath) => {
136
+ const fileDeps = fileDepsMap.get(packageJsonPath);
137
+ if (!fileDeps) return { results: [], updated: 0 };
138
+ const fileResults2 = allUpdateResults.filter((result) => {
139
+ return fileDeps[result.package] !== void 0;
140
+ });
141
+ const toUpdate = fileResults2.filter((r) => r.updated && !r.error);
91
142
  if (toUpdate.length > 0) {
92
143
  if (isDryRun) {
93
144
  const relativePath = path.relative(process.cwd(), packageJsonPath);
94
- logger.debug(`Would update ${toUpdate.length} dependencies in ${relativePath}`);
95
- continue;
145
+ const updateDetails = toUpdate.map((update) => `${update.package}\u2192${update.latestVersion}`).join(", ");
146
+ logger.log(
147
+ `Would update ${toUpdate.length} dependencies in ${relativePath}: ${updateDetails}`
148
+ );
149
+ return { results: fileResults2, updated: toUpdate.length };
96
150
  }
97
151
  const updated = await updatePackageJsonFileDirectly(
98
152
  packageJsonPath,
@@ -101,14 +155,32 @@ export default defineCommand({
101
155
  "^",
102
156
  fieldsToIgnore
103
157
  );
104
- totalUpdated += updated;
105
158
  if (updated > 0) {
106
159
  const relativePath = path.relative(process.cwd(), packageJsonPath);
107
- logger.debug(`Updated ${updated} dependencies in ${relativePath}`);
160
+ const updateDetails = toUpdate.map((update) => `${update.package}\u2192${update.latestVersion}`).join(", ");
161
+ logger.log(`Updated ${updated} dependencies in ${relativePath}: ${updateDetails}`);
108
162
  }
163
+ return { results: fileResults2, updated };
109
164
  }
165
+ return { results: fileResults2, updated: 0 };
166
+ });
167
+ const fileResults = await Promise.allSettled(fileUpdatePromises);
168
+ const processedFileResults = fileResults.map((result, index) => {
169
+ if (result.status === "fulfilled") {
170
+ return result.value;
171
+ } else {
172
+ const packageJsonPath = packageJsonFiles[index];
173
+ logger.warn(
174
+ `Failed to process ${packageJsonPath ? path.relative(process.cwd(), packageJsonPath) : `file at index ${index}`}: ${result.reason}`
175
+ );
176
+ return { results: [], updated: 0 };
177
+ }
178
+ });
179
+ let totalUpdated = 0;
180
+ for (const result of processedFileResults) {
181
+ totalUpdated += result.updated;
110
182
  }
111
- displayStructuredUpdateResults(allResults, packageJsonFiles, fileDepsMap, showDetails);
183
+ displayStructuredUpdateResults(allUpdateResults, packageJsonFiles, fileDepsMap, showDetails);
112
184
  if (totalUpdated === 0) {
113
185
  if (isDryRun) {
114
186
  logger.log("Dry run mode - no changes would be made");
@@ -117,16 +189,21 @@ export default defineCommand({
117
189
  }
118
190
  return;
119
191
  }
192
+ const action = isDryRun ? "Would update" : "Updated";
120
193
  if (packageJsonFiles.length > 1) {
121
194
  logger.log(
122
- `Updated ${totalUpdated} dependencies across ${packageJsonFiles.length} package.json files`
195
+ `${action} ${totalUpdated} dependencies across ${packageJsonFiles.length} package.json files`
123
196
  );
124
197
  } else {
125
- logger.log(`Updated ${totalUpdated} dependencies`);
198
+ logger.log(`${action} ${totalUpdated} dependencies`);
126
199
  }
127
- if (shouldInstall) {
128
- await handleInstallation();
129
- } else {
200
+ if (shouldInstall && totalUpdated > 0 && !isDryRun) {
201
+ await handleInstallation(isVerbose);
202
+ } else if (shouldInstall && totalUpdated === 0) {
203
+ logger.log("No dependencies were updated, skipping install");
204
+ } else if (shouldInstall && totalUpdated > 0 && isDryRun) {
205
+ logger.log("Dry run mode - no changes were made, skipping install");
206
+ } else if (!shouldInstall && totalUpdated > 0) {
130
207
  logger.log(
131
208
  "Run 'bun install' to apply the changes (use --no-install to skip automatic installation)"
132
209
  );
@@ -9,13 +9,19 @@ interface UpdateArgs {
9
9
  allowMajor?: boolean;
10
10
  concurrency?: number;
11
11
  ignoreFields?: string[];
12
+ verbose?: boolean;
12
13
  }
13
14
  export declare function validatePackageJson(): Promise<string>;
14
- export declare function prepareAllUpdateCandidates(): Promise<{
15
+ export declare function prepareAllUpdateCandidates(cwd?: string): Promise<{
15
16
  packageJsonFiles: string[];
16
17
  fileDepsMap: Map<string, Record<string, DependencyInfo>>;
17
18
  }>;
18
19
  export declare function checkPackageUpdatesForFile(fileDepsMap: Record<string, DependencyInfo>, args: UpdateArgs): Promise<UpdateResult[]>;
20
+ export declare function checkPackageUpdatesForAllFiles(globalDepsMap: Map<string, {
21
+ versionSpec: string;
22
+ locations: Set<string>;
23
+ files: Set<string>;
24
+ }>, args: UpdateArgs): Promise<UpdateResult[]>;
19
25
  export declare function updatePackageJsonFileDirectly(packageJsonPath: string, fileDepsMap: Record<string, DependencyInfo>, updatesToApply: UpdateResult[], savePrefix: string, fieldsToIgnore?: string[]): Promise<number>;
20
- export declare function handleInstallation(): Promise<void>;
26
+ export declare function handleInstallation(verbose?: boolean): Promise<void>;
21
27
  export {};