Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion src/utils/project-name.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { homedir } from 'os'
import path from 'path';
import { realpathSync } from 'fs';
import { execFileSync } from 'child_process';
import { logger } from './logger.js';
import { detectWorktree } from './worktree.js';
Expand Down Expand Up @@ -32,6 +33,15 @@ function findGitRepoRoot(dir: string): string | null {
}
}

function samePath(a: string, b: string): boolean {
const realOrResolve = (p: string) => { try { return realpathSync(path.resolve(p)); } catch { return path.resolve(p); } };
const left = realOrResolve(a);
const right = realOrResolve(b);
return process.platform === 'win32'
? left.toLowerCase() === right.toLowerCase()
: left === right;
}

export function getProjectName(cwd: string | null | undefined): string {
if (!cwd || cwd.trim() === '') {
logger.warn('PROJECT_NAME', 'Empty cwd provided, using fallback', { cwd });
Expand All @@ -43,8 +53,12 @@ export function getProjectName(cwd: string | null | undefined): string {
// #2663 — derive the project name from the git repo root when inside a repo so
// the name is stable across subdirectories/worktrees. Fall back to the cwd
// basename when not in a repo.
// #2882 — when cwd is a subdirectory of the repo root (monorepo package),
// use the cwd basename instead of the repo root basename.
const repoRoot = findGitRepoRoot(expanded);
const nameSource = repoRoot ?? expanded;
const nameSource = repoRoot && samePath(expanded, repoRoot)
? repoRoot
: expanded;
Comment thread
rodboev marked this conversation as resolved.
Outdated
Comment thread
rodboev marked this conversation as resolved.
Outdated

const basename = path.basename(nameSource);

Expand Down
12 changes: 10 additions & 2 deletions tests/utils/project-name.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,14 +79,22 @@ describe('getProjectName', () => {
rmSync(tmp, { recursive: true, force: true });
});

it('deep subdirectory inside a repo yields the repo-root name', () => {
expect(getProjectName(nestedDir)).toBe('my-real-repo');
it('deep subdirectory inside a repo yields the cwd basename', () => {
expect(getProjectName(nestedDir)).toBe('nested');
});

it('repo root itself yields the repo-root name', () => {
expect(getProjectName(repoRoot)).toBe('my-real-repo');
});

it('package directory inside a monorepo yields the package basename', () => {
const { mkdirSync } = require('fs');
const { join } = require('path');
const packageDir = join(repoRoot, 'packages', 'api');
mkdirSync(packageDir, { recursive: true });
expect(getProjectName(packageDir)).toBe('api');
});

it('non-repo path falls back to basename(cwd)', () => {
// A path that does not exist (and therefore cannot be in a repo) must
// fall back to basename(cwd) rather than throwing or returning a root.
Expand Down
Loading