@reliverse/dler 2.3.3 → 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.
@@ -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 {};
@@ -8,6 +8,7 @@ import {
8
8
  checkPackageUpdate,
9
9
  collectTargetDependencies,
10
10
  prepareDependenciesForUpdate,
11
+ prepareDependenciesForUpdateFromMap,
11
12
  runInstallCommand
12
13
  } from "./utils.js";
13
14
  export async function validatePackageJson() {
@@ -18,9 +19,9 @@ export async function validatePackageJson() {
18
19
  }
19
20
  return packageJsonPath;
20
21
  }
21
- export async function prepareAllUpdateCandidates() {
22
+ export async function prepareAllUpdateCandidates(cwd) {
22
23
  const glob = new Glob("**/package.json");
23
- const packageJsonFiles = [];
24
+ let packageJsonFiles = [];
24
25
  for await (const file of glob.scan({
25
26
  cwd: process.cwd(),
26
27
  onlyFiles: true
@@ -30,24 +31,34 @@ export async function prepareAllUpdateCandidates() {
30
31
  packageJsonFiles.push(fullPath);
31
32
  }
32
33
  }
34
+ if (cwd) {
35
+ const cwdPath = path.resolve(process.cwd(), cwd);
36
+ packageJsonFiles = packageJsonFiles.filter((filePath) => {
37
+ return filePath.startsWith(cwdPath + path.sep) || filePath === path.join(cwdPath, "package.json");
38
+ });
39
+ }
33
40
  if (packageJsonFiles.length === 0) {
34
41
  logger.warn("No package.json files found");
35
42
  return { packageJsonFiles: [], fileDepsMap: /* @__PURE__ */ new Map() };
36
43
  }
37
- logger.debug(`Found ${packageJsonFiles.length} package.json files`);
38
44
  const fileDepsMap = /* @__PURE__ */ new Map();
39
- for (const packageJsonPath of packageJsonFiles) {
45
+ const readPromises = packageJsonFiles.map(async (packageJsonPath) => {
40
46
  try {
41
47
  const packageJson = JSON.parse(await fs.readFile(packageJsonPath, { encoding: "utf8" }));
42
48
  const { map } = collectTargetDependencies(packageJson);
43
- fileDepsMap.set(packageJsonPath, map);
49
+ return { path: packageJsonPath, map };
44
50
  } catch (error) {
45
51
  logger.warn(
46
52
  `Failed to process ${packageJsonPath}: ${error instanceof Error ? error.message : String(error)}`
47
53
  );
54
+ return { path: packageJsonPath, map: {} };
48
55
  }
56
+ });
57
+ const readResults = await pMap(readPromises, (promise) => promise, { concurrency: 20 });
58
+ for (const result of readResults) {
59
+ fileDepsMap.set(result.path, result.map);
49
60
  }
50
- logger.debug(`Processing ${packageJsonFiles.length} package.json files`);
61
+ logger.debug(`Processing ${packageJsonFiles.length} package.json files...`);
51
62
  return { packageJsonFiles, fileDepsMap };
52
63
  }
53
64
  export async function checkPackageUpdatesForFile(fileDepsMap, args) {
@@ -81,6 +92,56 @@ export async function checkPackageUpdatesForFile(fileDepsMap, args) {
81
92
  { concurrency: args.concurrency || 5 }
82
93
  );
83
94
  }
95
+ export async function checkPackageUpdatesForAllFiles(globalDepsMap, args) {
96
+ const options = {
97
+ allowMajor: !!args.allowMajor,
98
+ savePrefix: "^",
99
+ // Use default prefix
100
+ concurrency: args.concurrency || 50
101
+ // Increased default concurrency for HTTP requests
102
+ };
103
+ const candidates = prepareDependenciesForUpdateFromMap(globalDepsMap, args);
104
+ if (candidates.length === 0) {
105
+ return [];
106
+ }
107
+ if (args.verbose) {
108
+ logger.debug(
109
+ `Checking ${candidates.length} unique dependencies across all files with concurrency ${options.concurrency}`
110
+ );
111
+ }
112
+ const promises = candidates.map(async (dep) => {
113
+ const depInfo = globalDepsMap.get(dep);
114
+ if (!depInfo?.versionSpec) {
115
+ return {
116
+ package: dep,
117
+ currentVersion: "unknown",
118
+ latestVersion: "unknown",
119
+ updated: false,
120
+ error: "Current version not found",
121
+ semverCompatible: false,
122
+ location: "unknown"
123
+ };
124
+ }
125
+ return checkPackageUpdate(dep, depInfo.versionSpec, depInfo.locations, options);
126
+ });
127
+ const results = await Promise.allSettled(promises);
128
+ return results.map((result, index) => {
129
+ if (result.status === "fulfilled") {
130
+ return result.value;
131
+ } else {
132
+ const dep = candidates[index] ?? "unknown";
133
+ return {
134
+ package: dep,
135
+ currentVersion: "unknown",
136
+ latestVersion: "unknown",
137
+ updated: false,
138
+ error: `Failed to check: ${result.reason}`,
139
+ semverCompatible: false,
140
+ location: "unknown"
141
+ };
142
+ }
143
+ });
144
+ }
84
145
  export async function updatePackageJsonFileDirectly(packageJsonPath, fileDepsMap, updatesToApply, savePrefix, fieldsToIgnore = []) {
85
146
  if (updatesToApply.length === 0) {
86
147
  return 0;
@@ -127,9 +188,9 @@ export async function updatePackageJsonFileDirectly(packageJsonPath, fileDepsMap
127
188
  return 0;
128
189
  }
129
190
  }
130
- export async function handleInstallation() {
191
+ export async function handleInstallation(verbose = false) {
131
192
  try {
132
- await runInstallCommand();
193
+ await runInstallCommand(verbose);
133
194
  logger.log("Installation completed successfully");
134
195
  } catch (error) {
135
196
  logger.warn(`Install failed: ${error instanceof Error ? error.message : String(error)}`);
@@ -48,6 +48,7 @@ export declare function isSemverCompatible(currentVersionRange: string, latestVe
48
48
  /**
49
49
  * Collect ALL dependencies from package.json.
50
50
  * Returns a map of dependency name to its version and all locations where it appears.
51
+ * Early filters out non-updateable dependencies to reduce processing.
51
52
  */
52
53
  export declare function collectTargetDependencies(pkg: PackageJsonWithCatalogs): {
53
54
  map: Record<string, DependencyInfo>;
@@ -61,9 +62,13 @@ export declare function applyVersionUpdate(pkg: PackageJsonWithCatalogs, depName
61
62
  */
62
63
  export declare function fetchVersionFromRegistry(packageName: string): Promise<string>;
63
64
  /**
64
- * Get latest version of a package
65
+ * Get latest version of a package with caching
65
66
  */
66
67
  export declare function getLatestVersion(packageName: string): Promise<string>;
68
+ /**
69
+ * Initialize persistent cache (call this at the start of update operations)
70
+ */
71
+ export declare function initializeCache(verbose?: boolean): Promise<void>;
67
72
  /**
68
73
  * Check if a package needs updating and get update information
69
74
  *
@@ -74,6 +79,18 @@ export declare function getLatestVersion(packageName: string): Promise<string>;
74
79
  * - When allowMajor=false: Only allows updates within semver range
75
80
  */
76
81
  export declare function checkPackageUpdate(packageName: string, versionSpec: string, locations: Set<string>, options: PackageCheckOptions): Promise<UpdateResult>;
82
+ /**
83
+ * Filter and prepare dependencies for updating with glob pattern support (optimized for Map input)
84
+ */
85
+ export declare function prepareDependenciesForUpdateFromMap(allDepsMap: Map<string, {
86
+ versionSpec: string;
87
+ locations: Set<string>;
88
+ files: Set<string>;
89
+ }>, args: {
90
+ name?: string[];
91
+ ignore?: string[];
92
+ ignoreFields?: string[];
93
+ }): string[];
77
94
  /**
78
95
  * Filter and prepare dependencies for updating with glob pattern support
79
96
  */
@@ -93,5 +110,5 @@ export declare function displayStructuredUpdateResults(results: UpdateResult[],
93
110
  /**
94
111
  * Run Bun install command
95
112
  */
96
- export declare function runInstallCommand(): Promise<void>;
113
+ export declare function runInstallCommand(verbose?: boolean): Promise<void>;
97
114
  export {};