@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.
- package/dist/cmds/add/cmd.d.ts +2 -0
- package/dist/cmds/add/cmd.js +153 -0
- package/dist/cmds/add/impl.d.ts +19 -0
- package/dist/cmds/add/impl.js +515 -0
- package/dist/cmds/biome/cmd.d.ts +1 -5
- package/dist/cmds/build/cmd.d.ts +1 -91
- package/dist/cmds/clean/cmd.d.ts +1 -14
- package/dist/cmds/clean/presets.js +7 -0
- package/dist/cmds/init/cmd.d.ts +1 -8
- package/dist/cmds/publish/cmd.d.ts +1 -32
- package/dist/cmds/rm/cmd.d.ts +2 -0
- package/dist/cmds/rm/cmd.js +133 -0
- package/dist/cmds/rm/impl.d.ts +17 -0
- package/dist/cmds/rm/impl.js +509 -0
- package/dist/cmds/senv/cmd.d.ts +1 -7
- package/dist/cmds/test/cmd.d.ts +1 -8
- package/dist/cmds/tsc/cache.d.ts +0 -1
- package/dist/cmds/tsc/cache.js +8 -19
- package/dist/cmds/tsc/cmd.d.ts +1 -14
- package/dist/cmds/tsc/impl.js +77 -61
- package/dist/cmds/unused/cmd.d.ts +2 -0
- package/dist/cmds/unused/cmd.js +105 -0
- package/dist/cmds/unused/impl.d.ts +16 -0
- package/dist/cmds/unused/impl.js +415 -0
- package/dist/cmds/update/cmd.d.ts +1 -11
- package/dist/cmds/update/cmd.js +113 -36
- package/dist/cmds/update/impl.d.ts +8 -2
- package/dist/cmds/update/impl.js +69 -8
- package/dist/cmds/update/utils.d.ts +19 -2
- package/dist/cmds/update/utils.js +149 -61
- package/dist/utils/cache.d.ts +31 -0
- package/dist/utils/cache.js +60 -0
- package/package.json +19 -18
|
@@ -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:
|
|
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;
|
package/dist/cmds/update/cmd.js
CHANGED
|
@@ -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
|
-
|
|
8
|
+
checkPackageUpdatesForAllFiles,
|
|
8
9
|
handleInstallation,
|
|
9
10
|
prepareAllUpdateCandidates,
|
|
10
11
|
updatePackageJsonFileDirectly,
|
|
11
12
|
validatePackageJson
|
|
12
13
|
} from "./impl.js";
|
|
13
|
-
import {
|
|
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
|
|
56
|
-
const allowMajor =
|
|
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
|
-
|
|
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
|
-
|
|
69
|
-
const
|
|
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
|
-
|
|
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
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
const toUpdate =
|
|
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
|
-
|
|
95
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
195
|
+
`${action} ${totalUpdated} dependencies across ${packageJsonFiles.length} package.json files`
|
|
123
196
|
);
|
|
124
197
|
} else {
|
|
125
|
-
logger.log(
|
|
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 {};
|