@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.
@@ -153,47 +153,60 @@ const hasProjectReferences = async (packagePath) => {
153
153
  return false;
154
154
  }
155
155
  };
156
- const findTscExecutable = async (packagePath) => {
157
- const localTscPath = join(packagePath, "node_modules", ".bin", "tsc");
158
- if (existsSync(localTscPath)) {
159
- return localTscPath;
160
- }
161
- let currentDir = resolve(packagePath, "..");
162
- while (currentDir !== "/") {
163
- const parentLocalTscPath = join(currentDir, "node_modules", ".bin", "tsc");
164
- if (existsSync(parentLocalTscPath)) {
165
- return parentLocalTscPath;
156
+ const findTypeScriptExecutable = async (packagePath, preferTsgo = false) => {
157
+ const findExecutable = async (name) => {
158
+ const localPath = join(packagePath, "node_modules", ".bin", name);
159
+ if (existsSync(localPath)) {
160
+ return localPath;
161
+ }
162
+ let currentDir = resolve(packagePath, "..");
163
+ while (currentDir !== "/") {
164
+ const parentLocalPath = join(currentDir, "node_modules", ".bin", name);
165
+ if (existsSync(parentLocalPath)) {
166
+ return parentLocalPath;
167
+ }
168
+ const parentDir = resolve(currentDir, "..");
169
+ if (parentDir === currentDir) {
170
+ break;
171
+ }
172
+ currentDir = parentDir;
166
173
  }
167
- const parentDir = resolve(currentDir, "..");
168
- if (parentDir === currentDir) {
169
- break;
174
+ const globalPath = await lookpath(name);
175
+ if (globalPath) {
176
+ return globalPath;
170
177
  }
171
- currentDir = parentDir;
172
- }
173
- const globalTscPath = await lookpath("tsc");
174
- if (globalTscPath) {
175
- return globalTscPath;
178
+ return null;
179
+ };
180
+ if (preferTsgo) {
181
+ const tsgoPath = await findExecutable("tsgo");
182
+ if (tsgoPath) {
183
+ return { executable: tsgoPath, usedTsgo: true };
184
+ }
185
+ const tscPath = await findExecutable("tsc");
186
+ return { executable: tscPath, usedTsgo: false };
187
+ } else {
188
+ const tscPath = await findExecutable("tsc");
189
+ return { executable: tscPath, usedTsgo: false };
176
190
  }
177
- return null;
178
191
  };
179
192
  const runTscCommand = async (packagePath, options = {}) => {
193
+ const { incremental = true, buildMode = false, preferTsgo = false } = options;
180
194
  try {
181
- const { incremental = true, buildMode = false, tscExecutable: providedTsc } = options;
182
- const tscExecutable = providedTsc ?? await findTscExecutable(packagePath);
183
- if (!tscExecutable) {
195
+ const { executable, usedTsgo } = await findTypeScriptExecutable(packagePath, preferTsgo);
196
+ if (!executable) {
184
197
  throw new Error(
185
- "TypeScript not found. Skipping package (install typescript locally or globally to enable type checking)."
198
+ `${preferTsgo ? "tsgo" : "tsc"} not found. Skipping package (install ${preferTsgo ? "@typescript/native-preview" : "typescript"} locally or globally to enable type checking).`
186
199
  );
187
200
  }
188
201
  let args;
189
202
  if (buildMode && await hasProjectReferences(packagePath)) {
190
- args = [tscExecutable, "--build"];
191
- if (incremental) {
203
+ args = [executable, "--build"];
204
+ if (incremental && !usedTsgo) {
192
205
  args.push("--incremental");
193
206
  }
194
207
  } else {
195
- args = [tscExecutable, "--noEmit"];
196
- if (incremental) {
208
+ args = [executable, "--noEmit"];
209
+ if (incremental && !usedTsgo) {
197
210
  args.push("--incremental");
198
211
  const tsBuildInfoPath = join(
199
212
  packagePath,
@@ -215,8 +228,18 @@ const runTscCommand = async (packagePath, options = {}) => {
215
228
  const exitCode = await proc.exited;
216
229
  return { stdout, stderr, exitCode };
217
230
  } catch (error) {
231
+ if (preferTsgo && error instanceof Error && !error.message.includes("TypeScript not found")) {
232
+ try {
233
+ logger.warn(`\u26A0\uFE0F tsgo failed, falling back to tsc: ${error.message}`);
234
+ return runTscCommand(packagePath, { ...options, preferTsgo: false });
235
+ } catch (fallbackError) {
236
+ throw new Error(
237
+ `Failed to spawn TypeScript compiler (both tsgo and tsc): ${fallbackError instanceof Error ? fallbackError.message : String(fallbackError)}`
238
+ );
239
+ }
240
+ }
218
241
  throw new Error(
219
- `Failed to spawn tsc: ${error instanceof Error ? error.message : String(error)}`
242
+ `Failed to spawn TypeScript compiler: ${error instanceof Error ? error.message : String(error)}`
220
243
  );
221
244
  }
222
245
  };
@@ -271,15 +294,15 @@ const checkRequiredDependencies = async (packagePath) => {
271
294
  try {
272
295
  const pkg = await readPackageJSON(packagePath);
273
296
  if (!pkg) {
274
- return { hasTypeScript: false, hasTypesBun: false };
297
+ return { hasTypeScript: false, hasNativePreview: false };
275
298
  }
276
299
  const deps = pkg.dependencies && typeof pkg.dependencies === "object" ? pkg.dependencies : {};
277
300
  const devDeps = pkg.devDependencies && typeof pkg.devDependencies === "object" ? pkg.devDependencies : {};
278
301
  const hasTypeScript = "typescript" in deps || "typescript" in devDeps;
279
- const hasTypesBun = "@types/bun" in deps || "@types/bun" in devDeps;
280
- return { hasTypeScript, hasTypesBun };
302
+ const hasNativePreview = "@typescript/native-preview" in deps || "@typescript/native-preview" in devDeps;
303
+ return { hasTypeScript, hasNativePreview };
281
304
  } catch {
282
- return { hasTypeScript: false, hasTypesBun: false };
305
+ return { hasTypeScript: false, hasNativePreview: false };
283
306
  }
284
307
  };
285
308
  const runTscOnPackage = async (pkg, monorepoRoot, options = {}) => {
@@ -303,19 +326,13 @@ const runTscOnPackage = async (pkg, monorepoRoot, options = {}) => {
303
326
  executionTime: Date.now() - startTime
304
327
  };
305
328
  }
306
- const { hasTypeScript, hasTypesBun } = await checkRequiredDependencies(pkg.path);
307
- if (!(hasTypeScript && hasTypesBun)) {
308
- const missing = [];
309
- if (!hasTypeScript) {
310
- missing.push("typescript");
311
- }
312
- if (!hasTypesBun) {
313
- missing.push("@types/bun");
314
- }
329
+ const { hasTypeScript, hasNativePreview } = await checkRequiredDependencies(pkg.path);
330
+ if (!(hasNativePreview || hasTypeScript)) {
315
331
  throw new Error(
316
- `Package ${pkg.name} has tsconfig.json but ${missing.join(" and ")} ${missing.length === 1 ? "is" : "are"} not listed in dependencies or devDependencies. Please add ${missing.join(" and ")} to this package's package.json (then run bun install).`
332
+ `Package ${pkg.name} has tsconfig.json but neither "@typescript/native-preview" nor "typescript" is listed in dependencies or devDependencies. Please add one of them to this package's package.json (then run bun install).`
317
333
  );
318
334
  }
335
+ const preferTsgo = hasNativePreview;
319
336
  if (cache) {
320
337
  const shouldSkip = await cache.shouldSkipPackage(pkg);
321
338
  if (shouldSkip) {
@@ -342,32 +359,35 @@ const runTscOnPackage = async (pkg, monorepoRoot, options = {}) => {
342
359
  logger.info(`\u{1F50D} Checking ${pkg.name}...`);
343
360
  }
344
361
  try {
345
- const tscExecutable = await findTscExecutable(pkg.path);
346
- if (!tscExecutable) {
362
+ const { executable: tsExecutable, usedTsgo } = await findTypeScriptExecutable(
363
+ pkg.path,
364
+ preferTsgo
365
+ );
366
+ if (!tsExecutable) {
347
367
  throw new Error(
348
- "TypeScript not found. Skipping package (install typescript locally or globally to enable type checking)."
368
+ `${preferTsgo ? "tsgo" : "tsc"} not found. Skipping package (install ${preferTsgo ? "@typescript/native-preview" : "typescript"} locally or globally to enable type checking).`
349
369
  );
350
370
  }
351
371
  if (verbose) {
352
- const normalizedTscPath = resolve(tscExecutable);
372
+ const normalizedExecPath = resolve(tsExecutable);
353
373
  const normalizedPackagePath = resolve(pkg.path);
354
374
  const normalizedMonorepoRoot = resolve(monorepoRoot);
355
- const relativeToPackage = relative(normalizedPackagePath, normalizedTscPath);
356
- const relativeToMonorepo = relative(normalizedMonorepoRoot, normalizedTscPath);
375
+ const relativeToPackage = relative(normalizedPackagePath, normalizedExecPath);
376
+ const relativeToMonorepo = relative(normalizedMonorepoRoot, normalizedExecPath);
357
377
  let source;
358
- if (normalizedTscPath.startsWith(normalizedPackagePath)) {
378
+ if (normalizedExecPath.startsWith(normalizedPackagePath)) {
359
379
  source = `local (${relativeToPackage})`;
360
- } else if (normalizedTscPath.startsWith(normalizedMonorepoRoot)) {
380
+ } else if (normalizedExecPath.startsWith(normalizedMonorepoRoot)) {
361
381
  source = `monorepo root (${relativeToMonorepo})`;
362
382
  } else {
363
- source = `global (${tscExecutable})`;
383
+ source = `global (${tsExecutable})`;
364
384
  }
365
- logger.info(` Using tsc: ${source}`);
385
+ logger.info(` Using ${usedTsgo ? "tsgo" : "tsc"}: ${source}`);
366
386
  }
367
387
  const result = await runTscCommand(pkg.path, {
368
388
  incremental,
369
389
  buildMode,
370
- tscExecutable
390
+ preferTsgo
371
391
  });
372
392
  const output = result.stdout + result.stderr;
373
393
  const filteredOutput = filterOutputLines(output, pkg.path, monorepoRoot);
@@ -404,9 +424,9 @@ const runTscOnPackage = async (pkg, monorepoRoot, options = {}) => {
404
424
  return tscResult;
405
425
  } catch (error) {
406
426
  const errorMessage = error instanceof Error ? error.message : String(error);
407
- if (errorMessage.includes("TypeScript not found") || errorMessage.includes("Executable not found")) {
427
+ if (errorMessage.includes(" not found") || errorMessage.includes("Executable not found")) {
408
428
  if (verbose) {
409
- logger.info(`\u23ED\uFE0F Skipping ${pkg.name} (TypeScript not installed in this package)`);
429
+ logger.info(`\u23ED\uFE0F Skipping ${pkg.name} (TypeScript compiler not installed in this package)`);
410
430
  }
411
431
  return {
412
432
  package: pkg,
@@ -446,14 +466,11 @@ const collectAllResults = async (packages, monorepoRoot, options = {}, cache) =>
446
466
  incremental = true,
447
467
  buildMode = false
448
468
  } = options;
449
- if (!verbose) {
450
- logger.info(`Processing ${packages.length} packages...`);
451
- }
452
469
  try {
453
470
  const tscResults = await pMap(
454
471
  packages,
455
472
  async (pkg, index) => {
456
- if (!verbose) {
473
+ if (verbose) {
457
474
  logger.info(`Processing ${pkg.name} (${index + 1}/${packages.length})...`);
458
475
  }
459
476
  return runTscOnPackage(pkg, monorepoRoot, {
@@ -655,8 +672,7 @@ export const runTscOnAllPackages = async (ignore, cwd, options = {}) => {
655
672
  }
656
673
  const { stopOnError = false } = options;
657
674
  logger.info(
658
- ` Checking ${packages.length} packages (concurrency: ${concurrency}, stopOnError: ${stopOnError})...
659
- `
675
+ `Checking ${packages.length} packages (concurrency: ${concurrency}, stopOnError: ${stopOnError})...`
660
676
  );
661
677
  if (verbose) {
662
678
  logger.info("\u{1F680} Starting TypeScript checks...\n");
@@ -0,0 +1,2 @@
1
+ declare const _default: any;
2
+ export default _default;
@@ -0,0 +1,105 @@
1
+ import { logger } from "@reliverse/relinka";
2
+ import { defineCommand, option } from "@reliverse/rempts-core";
3
+ import { type } from "arktype";
4
+ import { findUnusedDependencies } from "./impl.js";
5
+ export default defineCommand({
6
+ description: "Find unused dependencies in package.json files",
7
+ options: {
8
+ // Target package selection
9
+ target: option(type("string | undefined"), {
10
+ short: "t",
11
+ description: "Target workspace package(s) (from workspaces.packages). Use '.' for current directory package. Supports multiple packages (space-separated) and glob patterns."
12
+ }),
13
+ package: option(type("string | undefined"), {
14
+ description: "Target workspace package(s) (alias for --target). Use '.' for current directory package. Supports multiple packages (space-separated) and glob patterns."
15
+ }),
16
+ pkg: option(type("string | undefined"), {
17
+ description: "Target workspace package(s) (alias for --target). Use '.' for current directory package. Supports multiple packages (space-separated) and glob patterns."
18
+ }),
19
+ w: option(type("boolean | undefined"), {
20
+ description: "Check unused dependencies in root package.json"
21
+ }),
22
+ // Scope filtering
23
+ scope: option(type("'dev'|'prod'|'peer'|'optional' | undefined"), {
24
+ short: "s",
25
+ description: "Check specific dependency scope: dev, prod, peer, optional. If not specified, checks all scopes."
26
+ }),
27
+ dev: option(type("boolean | undefined"), {
28
+ short: "D",
29
+ description: "Check only devDependencies (shorthand for --scope dev)"
30
+ }),
31
+ prod: option(type("boolean | undefined"), {
32
+ short: "P",
33
+ description: "Check only dependencies (shorthand for --scope prod)"
34
+ }),
35
+ peer: option(type("boolean | undefined"), {
36
+ short: "R",
37
+ description: "Check only peerDependencies (shorthand for --scope peer)"
38
+ }),
39
+ optional: option(type("boolean | undefined"), {
40
+ short: "O",
41
+ description: "Check only optionalDependencies (shorthand for --scope optional)"
42
+ }),
43
+ // Analysis options
44
+ ignore: option(type("string | undefined"), {
45
+ short: "i",
46
+ description: "Comma-separated list of package names to ignore when checking for unused dependencies"
47
+ }),
48
+ includePeer: option(type("boolean | undefined"), {
49
+ description: "Include peerDependencies in the analysis (default: false)",
50
+ default: false
51
+ }),
52
+ // Other options
53
+ cwd: option(type("string | undefined"), {
54
+ description: "Working directory (monorepo root)"
55
+ }),
56
+ verbose: option(type("boolean | undefined"), {
57
+ short: "v",
58
+ description: "Verbose output"
59
+ })
60
+ },
61
+ handler: async ({ flags }) => {
62
+ try {
63
+ if (typeof process.versions.bun === "undefined") {
64
+ logger.error("\u274C This command requires Bun runtime.");
65
+ logger.error("Please run this command using Bun: bun dler unused");
66
+ process.exit(1);
67
+ }
68
+ logger.log("\u{1F50D} Finding unused dependencies...");
69
+ let scope;
70
+ if (flags.scope) {
71
+ scope = flags.scope;
72
+ } else if (flags.dev) {
73
+ scope = "dev";
74
+ } else if (flags.peer) {
75
+ scope = "peer";
76
+ } else if (flags.optional) {
77
+ scope = "optional";
78
+ }
79
+ const ignoreList = flags.ignore ? flags.ignore.split(",").map((pkg) => pkg.trim()).filter(Boolean) : void 0;
80
+ const options = {
81
+ target: flags.target || flags.package || flags.pkg,
82
+ w: flags.w,
83
+ scope,
84
+ ignore: ignoreList,
85
+ includePeer: flags.includePeer ?? false,
86
+ cwd: flags.cwd || void 0,
87
+ verbose: flags.verbose ?? false
88
+ };
89
+ await findUnusedDependencies(options);
90
+ } catch (error) {
91
+ logger.error("\n\u274C Failed to find unused dependencies:");
92
+ if (error instanceof Error) {
93
+ logger.error(error.message);
94
+ } else {
95
+ logger.error(String(error));
96
+ }
97
+ logger.log("");
98
+ logger.log("\u{1F4A1} Tips:");
99
+ logger.log(" \u2022 Ensure you're in a valid project directory with package.json");
100
+ logger.log(" \u2022 Use --verbose flag for more detailed output");
101
+ logger.log(" \u2022 Use --ignore flag to exclude specific packages from analysis");
102
+ process.exit(1);
103
+ }
104
+ }
105
+ });
@@ -0,0 +1,16 @@
1
+ export interface UnusedOptions {
2
+ target?: string;
3
+ w?: boolean;
4
+ scope?: "dev" | "prod" | "peer" | "optional";
5
+ ignore?: string[];
6
+ includePeer: boolean;
7
+ cwd?: string;
8
+ verbose: boolean;
9
+ }
10
+ export interface MonorepoInfo {
11
+ isMonorepo: boolean;
12
+ rootPath: string;
13
+ rootPackageJson: any;
14
+ workspacePackages?: string[];
15
+ }
16
+ export declare function findUnusedDependencies(options: UnusedOptions): Promise<void>;