From 9023b911de7b838b989eff14992ea0e715b58e2b Mon Sep 17 00:00:00 2001 From: Elijah Kotyluk Date: Mon, 15 Dec 2025 08:32:33 -0600 Subject: [PATCH 1/8] feat(cli): add cwd option. --- src/cli/help.ts | 1 + src/cli/index.ts | 18 ++++++++++++------ src/cli/internalMode.ts | 3 +++ 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/src/cli/help.ts b/src/cli/help.ts index 802196c..505a643 100644 --- a/src/cli/help.ts +++ b/src/cli/help.ts @@ -14,6 +14,7 @@ ${colors.bold}Commands:${colors.reset} internal Run intra-file identifier analysis only. ${colors.bold}General Options:${colors.reset} + --cwd Set the current working directory for analysis (default: process.cwd()) --help Show this help message --json Machine-readable output --silent Suppress all non-JSON output diff --git a/src/cli/index.ts b/src/cli/index.ts index 01bbcbd..b76861e 100644 --- a/src/cli/index.ts +++ b/src/cli/index.ts @@ -6,21 +6,26 @@ import { logUnusedExports, logUnusedFiles, logUnusedLocals, logVerbose, summary import { showHelp } from "./help"; import { parseArgs } from "./parser"; +import path from "path"; + (async () => { const args = parseArgs(process.argv.slice(2)); - const [command, commandTarget] = args._; + const [command, targetPath] = args._; + + const cwd = args.cwd ? path.resolve(process.cwd(), args.cwd) : process.cwd(); // Internal single-file analysis mode if (command === "internal") { if (args.help) { if (!args.silent) { log("Usage: devoid internal [options]"); - log("Run `devoid --help` for full command list."); + log("Run `devoid --help` for global options."); } process.exit(0); } - const filePath = commandTarget; + const filePath = targetPath ? path.resolve(cwd, targetPath) : null; + if (!filePath) { if (!args.silent) { log("Error: No file provided for internal analysis."); @@ -32,7 +37,7 @@ import { parseArgs } from "./parser"; disableLogPrefix(); const { runInternalMode } = await import("./internalMode.js"); - await runInternalMode(filePath, args); + await runInternalMode(filePath, { ...args, cwd }); process.exit(0); } @@ -62,7 +67,8 @@ import { parseArgs } from "./parser"; } // Project root path - const projectRoot = args._[0]; + const projectRoot = args._[0] ? path.resolve(cwd, args._[0]) : null; + if (!projectRoot) { if (!silent) { log("Error: No project path provided."); @@ -72,7 +78,7 @@ import { parseArgs } from "./parser"; } // Run full-project analysis - const results = analyzeProject(projectRoot, args); + const results = analyzeProject(projectRoot, { ...args, cwd }); // JSON output if (args.json) { diff --git a/src/cli/internalMode.ts b/src/cli/internalMode.ts index a38f773..471e2ad 100644 --- a/src/cli/internalMode.ts +++ b/src/cli/internalMode.ts @@ -1,4 +1,6 @@ import fs from "fs"; +import path from "path"; + import { analyzeLocalUsage } from "../core/locals/analyzeLocalUsage"; import { log } from "../utils"; import { colors } from "./colors"; @@ -16,6 +18,7 @@ import { heading } from "./format"; export async function runInternalMode(filePath: string, args: any): Promise { const SILENT_MODE = !!args.silent; let sourceText: string; + filePath = path.resolve(args.cwd, filePath) // Read file try { From 19ae9d1f7757c6f40d537fb175bcb9888220424a Mon Sep 17 00:00:00 2001 From: Elijah Kotyluk Date: Mon, 15 Dec 2025 08:41:37 -0600 Subject: [PATCH 2/8] fix(ci): lint/prettier. --- src/cli/internalMode.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cli/internalMode.ts b/src/cli/internalMode.ts index 471e2ad..bb3dec7 100644 --- a/src/cli/internalMode.ts +++ b/src/cli/internalMode.ts @@ -18,7 +18,7 @@ import { heading } from "./format"; export async function runInternalMode(filePath: string, args: any): Promise { const SILENT_MODE = !!args.silent; let sourceText: string; - filePath = path.resolve(args.cwd, filePath) + filePath = path.resolve(args.cwd, filePath); // Read file try { From 2b5cfb51eaba4741a9866abaebca07ee7782ad07 Mon Sep 17 00:00:00 2001 From: Elijah Kotyluk Date: Mon, 15 Dec 2025 08:54:07 -0600 Subject: [PATCH 3/8] chore(cwd): add helper function to check and validate cwd. --- src/cli/index.ts | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/src/cli/index.ts b/src/cli/index.ts index b76861e..d4fc9d2 100644 --- a/src/cli/index.ts +++ b/src/cli/index.ts @@ -6,13 +6,31 @@ import { logUnusedExports, logUnusedFiles, logUnusedLocals, logVerbose, summary import { showHelp } from "./help"; import { parseArgs } from "./parser"; +import { statSync } from "fs"; import path from "path"; +function resolveAndValidateCwd(rawCwd?: string): string { + const resolvedCwd = rawCwd ? path.resolve(process.cwd(), rawCwd) : process.cwd(); + + if (!rawCwd) return resolvedCwd; + + try { + if (!statSync(resolvedCwd).isDirectory()) { + throw new Error("--cwd is not a directory"); + } + } catch { + log(`Error: --cwd does not exist or is not a directory: ${resolvedCwd}`); + process.exit(1); + } + + return resolvedCwd; +} + (async () => { const args = parseArgs(process.argv.slice(2)); const [command, targetPath] = args._; - const cwd = args.cwd ? path.resolve(process.cwd(), args.cwd) : process.cwd(); + const cwd = resolveAndValidateCwd(args.cwd); // Internal single-file analysis mode if (command === "internal") { From abfe51a6512cdbe9b4b48ba8cd43e3f5781e6714 Mon Sep 17 00:00:00 2001 From: Elijah Kotyluk Date: Mon, 15 Dec 2025 09:26:34 -0600 Subject: [PATCH 4/8] refactor(cli): don't pass cwd since it's not needed yet. --- src/cli/index.ts | 4 ++-- src/cli/internalMode.ts | 2 -- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/cli/index.ts b/src/cli/index.ts index d4fc9d2..d5ebe05 100644 --- a/src/cli/index.ts +++ b/src/cli/index.ts @@ -55,7 +55,7 @@ function resolveAndValidateCwd(rawCwd?: string): string { disableLogPrefix(); const { runInternalMode } = await import("./internalMode.js"); - await runInternalMode(filePath, { ...args, cwd }); + await runInternalMode(filePath, args); process.exit(0); } @@ -96,7 +96,7 @@ function resolveAndValidateCwd(rawCwd?: string): string { } // Run full-project analysis - const results = analyzeProject(projectRoot, { ...args, cwd }); + const results = analyzeProject(projectRoot, args); // JSON output if (args.json) { diff --git a/src/cli/internalMode.ts b/src/cli/internalMode.ts index bb3dec7..a5d84bb 100644 --- a/src/cli/internalMode.ts +++ b/src/cli/internalMode.ts @@ -1,5 +1,4 @@ import fs from "fs"; -import path from "path"; import { analyzeLocalUsage } from "../core/locals/analyzeLocalUsage"; import { log } from "../utils"; @@ -18,7 +17,6 @@ import { heading } from "./format"; export async function runInternalMode(filePath: string, args: any): Promise { const SILENT_MODE = !!args.silent; let sourceText: string; - filePath = path.resolve(args.cwd, filePath); // Read file try { From 79353e4b0e955a99e56f46af5c4bcdbf60eaf868 Mon Sep 17 00:00:00 2001 From: Elijah Kotyluk Date: Mon, 15 Dec 2025 09:28:55 -0600 Subject: [PATCH 5/8] chore(internalMode): import readFileSync instead of entire fs module. --- src/cli/internalMode.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/cli/internalMode.ts b/src/cli/internalMode.ts index a5d84bb..60ff8c5 100644 --- a/src/cli/internalMode.ts +++ b/src/cli/internalMode.ts @@ -1,4 +1,4 @@ -import fs from "fs"; +import { readFileSync } from "fs"; import { analyzeLocalUsage } from "../core/locals/analyzeLocalUsage"; import { log } from "../utils"; @@ -20,7 +20,7 @@ export async function runInternalMode(filePath: string, args: any): Promise Date: Mon, 15 Dec 2025 10:00:12 -0600 Subject: [PATCH 6/8] test(cwd): add basic cwd flag tests. Update all node imports. --- scripts/verify-tarball.js | 8 +-- tests/cli/cli.basic.test.ts | 8 +-- tests/cli/cli.cwd.test.ts | 83 ++++++++++++++++++++++ tests/cli/utils/runCLI.ts | 17 +++++ tests/exports/conflictingExports.test.ts | 2 +- tests/exports/exportChains.test.ts | 6 +- tests/exports/exportGraphResolver.test.ts | 2 +- tests/exports/exportUsage.test.ts | 6 +- tests/exports/localPriority.test.ts | 2 +- tests/exports/multiHop.test.ts | 2 +- tests/exports/namedReexports.test.ts | 2 +- tests/exports/unresolvedReexports.test.ts | 2 +- tests/exports/wildcardExportChains.test.ts | 6 +- tests/exports/wildcardReexport.test.ts | 2 +- tests/imports/importGraph.test.ts | 6 +- tests/imports/mixedImports.test.ts | 2 +- tests/imports/typeOnlyImports.test.ts | 2 +- tests/imports/unresolvedImports.test.ts | 6 +- tests/integrate/barrel.test.ts | 2 +- tests/integrate/endToEnd.test.ts | 4 +- tests/integrate/realistic.test.ts | 6 +- tests/internal/basicInternalUsage.test.ts | 6 +- tests/internal/strictInternalUsage.test.ts | 6 +- tests/utils/fileWalker.test.ts | 4 +- tests/utils/pathNormalization.test.ts | 4 +- 25 files changed, 148 insertions(+), 48 deletions(-) create mode 100644 tests/cli/cli.cwd.test.ts create mode 100644 tests/cli/utils/runCLI.ts diff --git a/scripts/verify-tarball.js b/scripts/verify-tarball.js index e91c7f0..f4f8bf3 100644 --- a/scripts/verify-tarball.js +++ b/scripts/verify-tarball.js @@ -1,7 +1,7 @@ -const { execSync } = require("node:child_process"); -const fs = require("node:fs"); -const os = require("node:os"); -const path = require("node:path"); +const { execSync } = require("child_process"); +const fs = require("fs"); +const os = require("os"); +const path = require("path"); function run(cmd, cwd = process.cwd()) { console.log(`\n> ${cmd}`); diff --git a/tests/cli/cli.basic.test.ts b/tests/cli/cli.basic.test.ts index 9b8e751..4fd16b9 100644 --- a/tests/cli/cli.basic.test.ts +++ b/tests/cli/cli.basic.test.ts @@ -1,10 +1,10 @@ // tests/cli.basic.test.ts -import assert from "node:assert/strict"; -import { spawnSync } from "node:child_process"; -import fs from "node:fs"; -import { resolve } from "node:path"; +import assert from "assert/strict"; +import { spawnSync } from "child_process"; +import fs from "fs"; import { test } from "node:test"; +import { resolve } from "path"; const CLI_PATH = resolve("bin", "devoid.js"); const PKG_PATH = resolve("package.json"); diff --git a/tests/cli/cli.cwd.test.ts b/tests/cli/cli.cwd.test.ts new file mode 100644 index 0000000..d848e23 --- /dev/null +++ b/tests/cli/cli.cwd.test.ts @@ -0,0 +1,83 @@ +import assert from "assert/strict"; +import fs from "fs"; +import test from "node:test"; +import os from "os"; +import path from "path"; + +import { runCLI } from "./utils/runCLI"; + +function writeFile(filePath: string, contents: string) { + fs.mkdirSync(path.dirname(filePath), { recursive: true }); + fs.writeFileSync(filePath, contents, "utf8"); +} + +test("--cwd: resolves project root relative to cwd", () => { + const tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), "devoid-cwd-")); + const projectDir = path.join(tmpRoot, "project"); + const srcDir = path.join(projectDir, "src"); + + writeFile(path.join(srcDir, "a.ts"), `export const foo = 123;\n`); + + const { code, stdout } = runCLI(["--cwd", "project", "src", "--exports"], { cwd: tmpRoot }); + + assert.equal(code, 0); + assert.match(stdout, /Unused Exports/i); + assert.match(stdout, /\bfoo\b/); +}); + +test("--cwd: resolves internal mode file path relative to cwd", () => { + const tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), "devoid-cwd-")); + const projectDir = path.join(tmpRoot, "project"); + const srcDir = path.join(projectDir, "src"); + + writeFile( + path.join(srcDir, "internal.ts"), + ` +function unusedFn() {} +function usedFn() {} +usedFn(); +`, + ); + + const { code, stdout } = runCLI(["--cwd", "project", "internal", "src/internal.ts"], { + cwd: tmpRoot, + }); + + assert.equal(code, 0); + assert.match(stdout, /Internal Usage Analysis/i); + assert.match(stdout, /\bunusedFn\b/); +}); + +test("--cwd: errors if directory does not exist", () => { + const tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), "devoid-cwd-")); + + const { code, stdout } = runCLI(["--cwd", "nope", "src", "--exports"], { cwd: tmpRoot }); + + assert.equal(code, 1); + assert.match(stdout, /--cwd/i); + assert.match(stdout, /does not exist/i); +}); + +test("--cwd: errors if path exists but is not a directory", () => { + const tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), "devoid-cwd-")); + const filePath = path.join(tmpRoot, "not-a-dir.txt"); + fs.writeFileSync(filePath, "hi", "utf8"); + + const { code, stdout } = runCLI(["--cwd", "not-a-dir.txt", "src"], { cwd: tmpRoot }); + + assert.equal(code, 1); + assert.match(stdout, /--cwd/i); + assert.match(stdout, /not a directory/i); +}); + +test("--cwd: works without --cwd (baseline behavior unchanged)", () => { + const tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), "devoid-cwd-")); + const srcDir = path.join(tmpRoot, "src"); + + writeFile(path.join(srcDir, "a.ts"), `export const foo = 123;\n`); + + const { code, stdout } = runCLI(["src", "--exports"], { cwd: tmpRoot }); + + assert.equal(code, 0); + assert.match(stdout, /\bfoo\b/); +}); diff --git a/tests/cli/utils/runCLI.ts b/tests/cli/utils/runCLI.ts new file mode 100644 index 0000000..4a2098c --- /dev/null +++ b/tests/cli/utils/runCLI.ts @@ -0,0 +1,17 @@ +import { spawnSync } from "child_process"; +import path from "path"; + +const NODE = process.execPath; +const CLI_ENTRY = path.resolve(__dirname, "../../../bin/devoid.js"); + +export function runCLI(args: string[], opts?: { cwd?: string }) { + const result = spawnSync(NODE, [CLI_ENTRY, ...args], { + cwd: opts?.cwd, + encoding: "utf8", + }); + + return { + code: result.status ?? 1, + stdout: (result.stdout ?? "") + (result.stderr ?? ""), + }; +} diff --git a/tests/exports/conflictingExports.test.ts b/tests/exports/conflictingExports.test.ts index 631a3ec..2cc2b75 100644 --- a/tests/exports/conflictingExports.test.ts +++ b/tests/exports/conflictingExports.test.ts @@ -1,5 +1,5 @@ +import assert from "assert/strict"; import fs from "fs"; -import assert from "node:assert/strict"; import test from "node:test"; import path from "path"; diff --git a/tests/exports/exportChains.test.ts b/tests/exports/exportChains.test.ts index 00fbb99..636e2de 100644 --- a/tests/exports/exportChains.test.ts +++ b/tests/exports/exportChains.test.ts @@ -1,7 +1,7 @@ -import assert from "node:assert"; -import fs from "node:fs"; -import path from "node:path"; +import assert from "assert"; +import fs from "fs"; import test from "node:test"; +import path from "path"; import { analyzeExportUsage } from "../../src/core/exports/exportUsage"; import { scanExports } from "../../src/core/exports/scanExports"; diff --git a/tests/exports/exportGraphResolver.test.ts b/tests/exports/exportGraphResolver.test.ts index d5e5086..90cc3d1 100644 --- a/tests/exports/exportGraphResolver.test.ts +++ b/tests/exports/exportGraphResolver.test.ts @@ -1,5 +1,5 @@ +import assert from "assert/strict"; import fs from "fs"; -import assert from "node:assert/strict"; import test from "node:test"; import path from "path"; diff --git a/tests/exports/exportUsage.test.ts b/tests/exports/exportUsage.test.ts index e743cbe..7c9f78e 100644 --- a/tests/exports/exportUsage.test.ts +++ b/tests/exports/exportUsage.test.ts @@ -1,7 +1,7 @@ -import assert from "node:assert"; -import fs from "node:fs"; -import path from "node:path"; +import assert from "assert"; +import fs from "fs"; import test from "node:test"; +import path from "path"; import { analyzeExportUsage } from "../../src/core/exports/exportUsage"; import { scanExports } from "../../src/core/exports/scanExports.js"; diff --git a/tests/exports/localPriority.test.ts b/tests/exports/localPriority.test.ts index 28232bd..c97eeed 100644 --- a/tests/exports/localPriority.test.ts +++ b/tests/exports/localPriority.test.ts @@ -1,5 +1,5 @@ +import assert from "assert/strict"; import fs from "fs"; -import assert from "node:assert/strict"; import test from "node:test"; import path from "path"; diff --git a/tests/exports/multiHop.test.ts b/tests/exports/multiHop.test.ts index 5dc75e4..8d845fa 100644 --- a/tests/exports/multiHop.test.ts +++ b/tests/exports/multiHop.test.ts @@ -1,5 +1,5 @@ +import assert from "assert/strict"; import fs from "fs"; -import assert from "node:assert/strict"; import test from "node:test"; import path from "path"; diff --git a/tests/exports/namedReexports.test.ts b/tests/exports/namedReexports.test.ts index 9401456..dd82624 100644 --- a/tests/exports/namedReexports.test.ts +++ b/tests/exports/namedReexports.test.ts @@ -1,5 +1,5 @@ +import assert from "assert/strict"; import fs from "fs"; -import assert from "node:assert/strict"; import test from "node:test"; import path from "path"; diff --git a/tests/exports/unresolvedReexports.test.ts b/tests/exports/unresolvedReexports.test.ts index 4d60568..ee0c39d 100644 --- a/tests/exports/unresolvedReexports.test.ts +++ b/tests/exports/unresolvedReexports.test.ts @@ -1,5 +1,5 @@ +import assert from "assert/strict"; import fs from "fs"; -import assert from "node:assert/strict"; import test from "node:test"; import path from "path"; diff --git a/tests/exports/wildcardExportChains.test.ts b/tests/exports/wildcardExportChains.test.ts index 172e0a5..76a7660 100644 --- a/tests/exports/wildcardExportChains.test.ts +++ b/tests/exports/wildcardExportChains.test.ts @@ -1,7 +1,7 @@ -import assert from "node:assert"; -import fs from "node:fs"; -import path from "node:path"; +import assert from "assert"; +import fs from "fs"; import test from "node:test"; +import path from "path"; import { analyzeExportUsage } from "../../src/core/exports/exportUsage"; import { scanExports } from "../../src/core/exports/scanExports.js"; diff --git a/tests/exports/wildcardReexport.test.ts b/tests/exports/wildcardReexport.test.ts index b2c63b3..77c6427 100644 --- a/tests/exports/wildcardReexport.test.ts +++ b/tests/exports/wildcardReexport.test.ts @@ -1,5 +1,5 @@ +import assert from "assert/strict"; import fs from "fs"; -import assert from "node:assert/strict"; import test from "node:test"; import path from "path"; diff --git a/tests/imports/importGraph.test.ts b/tests/imports/importGraph.test.ts index 10bb803..22defd6 100644 --- a/tests/imports/importGraph.test.ts +++ b/tests/imports/importGraph.test.ts @@ -1,7 +1,7 @@ -import assert from "node:assert"; -import fs from "node:fs"; -import path from "node:path"; +import assert from "assert"; +import fs from "fs"; import test from "node:test"; +import path from "path"; import { buildImportGraph } from "../../src/core/imports/buildImportGraph"; import { loadTSConfig } from "../../src/core/tsconfig/tsconfigLoader"; diff --git a/tests/imports/mixedImports.test.ts b/tests/imports/mixedImports.test.ts index e9b100b..8618d12 100644 --- a/tests/imports/mixedImports.test.ts +++ b/tests/imports/mixedImports.test.ts @@ -1,5 +1,5 @@ +import assert from "assert"; import fs from "fs"; -import assert from "node:assert"; import test from "node:test"; import path from "path"; diff --git a/tests/imports/typeOnlyImports.test.ts b/tests/imports/typeOnlyImports.test.ts index d3c15df..52508c4 100644 --- a/tests/imports/typeOnlyImports.test.ts +++ b/tests/imports/typeOnlyImports.test.ts @@ -1,5 +1,5 @@ +import assert from "assert"; import fs from "fs"; -import assert from "node:assert"; import test from "node:test"; import path from "path"; diff --git a/tests/imports/unresolvedImports.test.ts b/tests/imports/unresolvedImports.test.ts index 52cd317..deaa88b 100644 --- a/tests/imports/unresolvedImports.test.ts +++ b/tests/imports/unresolvedImports.test.ts @@ -1,7 +1,7 @@ -import assert from "node:assert"; -import fs from "node:fs"; -import path from "node:path"; +import assert from "assert"; +import fs from "fs"; import test from "node:test"; +import path from "path"; import { analyzeExportUsage } from "../../src/core/exports/exportUsage"; import { scanExports } from "../../src/core/exports/scanExports.js"; diff --git a/tests/integrate/barrel.test.ts b/tests/integrate/barrel.test.ts index a5da526..050c35c 100644 --- a/tests/integrate/barrel.test.ts +++ b/tests/integrate/barrel.test.ts @@ -1,5 +1,5 @@ +import assert from "assert/strict"; import fs from "fs"; -import assert from "node:assert/strict"; import test from "node:test"; import path from "path"; diff --git a/tests/integrate/endToEnd.test.ts b/tests/integrate/endToEnd.test.ts index 7c1f660..e7bebda 100644 --- a/tests/integrate/endToEnd.test.ts +++ b/tests/integrate/endToEnd.test.ts @@ -1,6 +1,6 @@ -import assert from "node:assert"; -import path from "node:path"; +import assert from "assert"; import test from "node:test"; +import path from "path"; import { analyzeProject } from "../../src/core/analyzer"; diff --git a/tests/integrate/realistic.test.ts b/tests/integrate/realistic.test.ts index 6f6a886..0686dee 100644 --- a/tests/integrate/realistic.test.ts +++ b/tests/integrate/realistic.test.ts @@ -1,7 +1,7 @@ -import assert from "node:assert/strict"; -import fs from "node:fs"; -import path from "node:path"; +import assert from "assert/strict"; +import fs from "fs"; import test from "node:test"; +import path from "path"; import { analyzeProject } from "../../src/core/analyzer.js"; diff --git a/tests/internal/basicInternalUsage.test.ts b/tests/internal/basicInternalUsage.test.ts index 40068e2..51866e5 100644 --- a/tests/internal/basicInternalUsage.test.ts +++ b/tests/internal/basicInternalUsage.test.ts @@ -1,7 +1,7 @@ -import assert from "node:assert"; -import fs from "node:fs"; -import path from "node:path"; +import assert from "assert"; +import fs from "fs"; import test from "node:test"; +import path from "path"; import { analyzeLocalUsage } from "../../src/core/locals/analyzeLocalUsage"; diff --git a/tests/internal/strictInternalUsage.test.ts b/tests/internal/strictInternalUsage.test.ts index 96496cd..809c28e 100644 --- a/tests/internal/strictInternalUsage.test.ts +++ b/tests/internal/strictInternalUsage.test.ts @@ -1,7 +1,7 @@ -import assert from "node:assert"; -import fs from "node:fs"; -import path from "node:path"; +import assert from "assert"; +import fs from "fs"; import test from "node:test"; +import path from "path"; import { analyzeLocalUsage } from "../../src/core/locals/analyzeLocalUsage"; diff --git a/tests/utils/fileWalker.test.ts b/tests/utils/fileWalker.test.ts index c3304b8..0503021 100644 --- a/tests/utils/fileWalker.test.ts +++ b/tests/utils/fileWalker.test.ts @@ -1,6 +1,6 @@ -import assert from "node:assert"; -import path from "node:path"; +import assert from "assert"; import test from "node:test"; +import path from "path"; import { walkFiles } from "../../src/core/fileSystem/walkFiles"; diff --git a/tests/utils/pathNormalization.test.ts b/tests/utils/pathNormalization.test.ts index 928941b..58f198e 100644 --- a/tests/utils/pathNormalization.test.ts +++ b/tests/utils/pathNormalization.test.ts @@ -1,6 +1,6 @@ -import assert from "node:assert"; -import path from "node:path"; +import assert from "assert"; import test from "node:test"; +import path from "path"; import { normalizeFilePath } from "../../src/core/fileSystem/normalizePath"; From 35b5f713942d09eb82a1651b9d1aba16fc96bc00 Mon Sep 17 00:00:00 2001 From: Elijah Kotyluk Date: Mon, 15 Dec 2025 13:48:07 -0600 Subject: [PATCH 7/8] chore(cwd): add better error messaging. --- src/cli/index.ts | 43 +++++++++++++++++++++++++++++++++++++++---- 1 file changed, 39 insertions(+), 4 deletions(-) diff --git a/src/cli/index.ts b/src/cli/index.ts index d5ebe05..2dd796c 100644 --- a/src/cli/index.ts +++ b/src/cli/index.ts @@ -9,17 +9,52 @@ import { parseArgs } from "./parser"; import { statSync } from "fs"; import path from "path"; +export function isNodeError(error: unknown): error is NodeJS.ErrnoException { + return ( + typeof error === "object" && + error !== null && + "code" in error && + typeof error?.code === "string" + ); +} + function resolveAndValidateCwd(rawCwd?: string): string { const resolvedCwd = rawCwd ? path.resolve(process.cwd(), rawCwd) : process.cwd(); if (!rawCwd) return resolvedCwd; + let stat; + try { - if (!statSync(resolvedCwd).isDirectory()) { - throw new Error("--cwd is not a directory"); + stat = statSync(resolvedCwd); + + if (!stat.isDirectory()) { + log(`Error: --cwd is not a directory: ${resolvedCwd}`); + process.exit(1); + } + } catch (error) { + if (isNodeError(error)) { + switch (error.code) { + case "ENOENT": + log(`Error: --cwd path does not exist:\n ${resolvedCwd}`); + break; + + case "ENOTDIR": + log(`Error: --cwd contains a non-directory segment:\n ${resolvedCwd}`); + break; + + case "EACCES": + case "EPERM": + log(`Error: Permission denied accessing --cwd:\n ${resolvedCwd}`); + break; + + default: + log(`Error: Unable to access --cwd:\n ${resolvedCwd}`); + } + } else { + log(`Error: Unable to access --cwd:\n ${resolvedCwd}`); } - } catch { - log(`Error: --cwd does not exist or is not a directory: ${resolvedCwd}`); + process.exit(1); } From 688d45afccb087fc94b716a721df82d9b6597879 Mon Sep 17 00:00:00 2001 From: Elijah Kotyluk Date: Mon, 15 Dec 2025 13:50:09 -0600 Subject: [PATCH 8/8] chore(cli): lint fixes. --- src/cli/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cli/index.ts b/src/cli/index.ts index 2dd796c..1cf0d31 100644 --- a/src/cli/index.ts +++ b/src/cli/index.ts @@ -52,7 +52,7 @@ function resolveAndValidateCwd(rawCwd?: string): string { log(`Error: Unable to access --cwd:\n ${resolvedCwd}`); } } else { - log(`Error: Unable to access --cwd:\n ${resolvedCwd}`); + log(`Error: Unable to access --cwd:\n ${resolvedCwd}`); } process.exit(1);