From 8e7cc63fd15c474de82ba37c767cfb24d9b108c5 Mon Sep 17 00:00:00 2001 From: Ryan Bahan Date: Thu, 2 Apr 2026 17:13:51 -0600 Subject: [PATCH] Remove commondir dependency, inline as commonParentDirectory() The commondir package is a tiny, unmaintained (~2014) utility. Inline its logic as an exported commonParentDirectory() function in path.ts, removing both the runtime dep and @types/commondir. Added parity tests covering the original package's test cases. Co-Authored-By: Claude Opus 4.6 (1M context) --- packages/cli-kit/package.json | 2 -- packages/cli-kit/src/public/node/path.test.ts | 36 ++++++++++++++++++- packages/cli-kit/src/public/node/path.ts | 20 +++++++++-- pnpm-lock.yaml | 16 --------- 4 files changed, 53 insertions(+), 21 deletions(-) diff --git a/packages/cli-kit/package.json b/packages/cli-kit/package.json index 1df7f81a0e5..e102e827e5d 100644 --- a/packages/cli-kit/package.json +++ b/packages/cli-kit/package.json @@ -121,7 +121,6 @@ "chalk": "5.4.1", "change-case": "4.1.2", "color-json": "3.0.5", - "commondir": "1.0.1", "conf": "11.0.2", "deepmerge": "4.3.1", "dotenv": "16.4.7", @@ -165,7 +164,6 @@ "zod": "3.24.4" }, "devDependencies": { - "@types/commondir": "^1.0.0", "@types/diff": "^5.2.3", "@types/fs-extra": "9.0.13", "@types/gradient-string": "^1.1.2", diff --git a/packages/cli-kit/src/public/node/path.test.ts b/packages/cli-kit/src/public/node/path.test.ts index e2dc5d69c8d..4972b3ac276 100644 --- a/packages/cli-kit/src/public/node/path.test.ts +++ b/packages/cli-kit/src/public/node/path.test.ts @@ -1,4 +1,4 @@ -import {relativizePath, normalizePath, cwd, sniffForPath} from './path.js' +import {relativizePath, normalizePath, cwd, sniffForPath, commonParentDirectory} from './path.js' import {describe, test, expect} from 'vitest' describe('relativize', () => { @@ -25,6 +25,40 @@ describe('cwd', () => { }) }) +describe('commonParentDirectory', () => { + // Parity tests with the original 'commondir' npm package (v1.0.1) + test('finds common parent for paths sharing a prefix', () => { + expect(commonParentDirectory('/foo', '/foo/bar')).toBe('/foo') + expect(commonParentDirectory('/foo/bar', '/foo//bar/baz')).toBe('/foo/bar') + }) + + test('finds deepest common ancestor', () => { + expect(commonParentDirectory('/a/b/c', '/a/b')).toBe('/a/b') + expect(commonParentDirectory('/a/b', '/a/b/c/d/e')).toBe('/a/b') + }) + + test('returns root when paths diverge at top level', () => { + expect(commonParentDirectory('/x/y/z/w', '/xy/z')).toBe('/') + }) + + test('handles Windows-style paths', () => { + expect(commonParentDirectory('X:\\foo', 'X:\\\\foo\\bar')).toBe('X:/foo') + expect(commonParentDirectory('X:\\a\\b\\c', 'X:\\a\\b')).toBe('X:/a/b') + }) + + test('returns root for completely divergent Windows paths', () => { + expect(commonParentDirectory('X:\\x\\y\\z\\w', '\\\\xy\\z')).toBe('/') + }) + + test('returns root for single-component paths', () => { + expect(commonParentDirectory('/', '/')).toBe('/') + }) + + test('handles identical paths', () => { + expect(commonParentDirectory('/a/b/c', '/a/b/c')).toBe('/a/b/c') + }) +}) + describe('sniffForPath', () => { test('returns the path if provided', () => { // Given diff --git a/packages/cli-kit/src/public/node/path.ts b/packages/cli-kit/src/public/node/path.ts index 20ce55840cf..697dad3ed92 100644 --- a/packages/cli-kit/src/public/node/path.ts +++ b/packages/cli-kit/src/public/node/path.ts @@ -1,4 +1,3 @@ -import commondir from 'commondir' import { relative, dirname as patheDirname, @@ -106,6 +105,23 @@ export function parsePath(path: string): {root: string; dir: string; base: strin return parse(path) } +/** + * Returns the longest common parent directory of two absolute paths. + * + * @param first - First absolute path. + * @param second - Second absolute path. + * @returns The common parent directory, or '/' if they share only the root. + */ +export function commonParentDirectory(first: string, second: string): string { + const firstParts = first.split(/\/+|\\+/) + const secondParts = second.split(/\/+|\\+/) + let i = 0 + while (i < firstParts.length && i < secondParts.length && firstParts[i] === secondParts[i]) { + i++ + } + return i > 1 ? firstParts.slice(0, i).join('/') : '/' +} + /** * Given an absolute filesystem path, it makes it relative to * the current working directory. This is useful when logging paths @@ -117,7 +133,7 @@ export function parsePath(path: string): {root: string; dir: string; base: strin * @returns Relativized path. */ export function relativizePath(path: string, dir: string = cwd()): string { - const result = commondir([path, dir]) + const result = commonParentDirectory(path, dir) const relativePath = relative(dir, path) // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 342c198900b..c672a983964 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -357,9 +357,6 @@ importers: color-json: specifier: 3.0.5 version: 3.0.5 - commondir: - specifier: 1.0.1 - version: 1.0.1 conf: specifier: 11.0.2 version: 11.0.2 @@ -484,9 +481,6 @@ importers: specifier: 3.24.4 version: 3.24.4 devDependencies: - '@types/commondir': - specifier: ^1.0.0 - version: 1.0.2 '@types/diff': specifier: ^5.2.3 version: 5.2.3 @@ -3906,9 +3900,6 @@ packages: '@types/cli-progress@3.11.6': resolution: {integrity: sha512-cE3+jb9WRlu+uOSAugewNpITJDt1VF8dHOopPO4IABFc3SXYL5WE/+PTz/FCdZRRfIujiWW3n3aMbv1eIGVRWA==} - '@types/commondir@1.0.2': - resolution: {integrity: sha512-ugIRUhO6vLS6Pi5Pz/0yuMYX7Q1rsEpXNrU7ef6rVdH+cb3hTDz8HL55C0QINDqMB4ZWsrE8lLWyYUbZKPhduQ==} - '@types/deep-eql@4.0.2': resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} @@ -4862,9 +4853,6 @@ packages: resolution: {integrity: sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA==} engines: {node: '>=4.0.0'} - commondir@1.0.1: - resolution: {integrity: sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==} - compress-commons@4.1.2: resolution: {integrity: sha512-D3uMHtGc/fcO1Gt1/L7i1e33VOvD4A9hfQLP+6ewd+BvG/gQ84Yh4oftEhAdjSMgBgwGL+jsppT7JYNpo6MHHg==} engines: {node: '>= 10'} @@ -13280,8 +13268,6 @@ snapshots: dependencies: '@types/node': 18.19.70 - '@types/commondir@1.0.2': {} - '@types/deep-eql@4.0.2': {} '@types/diff@5.2.3': {} @@ -14345,8 +14331,6 @@ snapshots: common-tags@1.8.2: {} - commondir@1.0.1: {} - compress-commons@4.1.2: dependencies: buffer-crc32: 0.2.13