From a28fc5508d69f21d7f4d1adb6d5964a96165d727 Mon Sep 17 00:00:00 2001 From: Paul Valladares <85648028+dreyfus92@users.noreply.github.com> Date: Mon, 9 Mar 2026 13:33:38 -0500 Subject: [PATCH 1/2] fix(prompts): submit initial directory value in path prompt --- .changeset/tricky-states-tease.md | 5 +++++ packages/prompts/src/path.ts | 9 ++++++++ packages/prompts/test/path.test.ts | 35 ++++++++++++++++++++++++++++++ 3 files changed, 49 insertions(+) create mode 100644 .changeset/tricky-states-tease.md diff --git a/.changeset/tricky-states-tease.md b/.changeset/tricky-states-tease.md new file mode 100644 index 00000000..03551af9 --- /dev/null +++ b/.changeset/tricky-states-tease.md @@ -0,0 +1,5 @@ +--- +"@clack/prompts": patch +--- + +Fix `path` directory mode so pressing Enter with an existing directory `initialValue` submits that current directory instead of the first child option, and add regression coverage for immediate submit and child-directory navigation. diff --git a/packages/prompts/src/path.ts b/packages/prompts/src/path.ts index d575ab33..8c3615ee 100644 --- a/packages/prompts/src/path.ts +++ b/packages/prompts/src/path.ts @@ -39,12 +39,14 @@ export const path = (opts: PathOptions) => { try { let searchPath: string; + let userInputIsDirectory = false; if (!existsSync(userInput)) { searchPath = dirname(userInput); } else { const stat = lstatSync(userInput); if (stat.isDirectory()) { + userInputIsDirectory = true; searchPath = userInput; } else { searchPath = dirname(userInput); @@ -65,6 +67,13 @@ export const path = (opts: PathOptions) => { ({ path, isDirectory }) => path.startsWith(userInput) && (isDirectory || !opts.directory) ); + if (opts.directory && userInputIsDirectory) { + items.unshift({ + name: userInput, + path: userInput, + isDirectory: true, + }); + } return items.map((item) => ({ value: item.path, })); diff --git a/packages/prompts/test/path.test.ts b/packages/prompts/test/path.test.ts index 24f7d76d..86e69e96 100644 --- a/packages/prompts/test/path.test.ts +++ b/packages/prompts/test/path.test.ts @@ -163,6 +163,41 @@ describe.each(['true', 'false'])('text (isCI = %s)', (isCI) => { expect(value).toBe('/tmp/foo'); }); + test('directory mode submits initial directory value on enter', async () => { + const result = prompts.path({ + message: 'foo', + root: '/tmp/', + initialValue: '/tmp/', + directory: true, + input, + output, + }); + + input.emit('keypress', '', { name: 'return' }); + + const value = await result; + + expect(value).toBe('/tmp/'); + }); + + test('directory mode can navigate from initial directory to child directory', async () => { + const result = prompts.path({ + message: 'foo', + root: '/tmp/', + initialValue: '/tmp/', + directory: true, + input, + output, + }); + + input.emit('keypress', '', { name: 'down' }); + input.emit('keypress', '', { name: 'return' }); + + const value = await result; + + expect(value).toBe('/tmp/foo'); + }); + test('default mode allows selecting files', async () => { const result = prompts.path({ message: 'foo', From d5f525763bda8044037b8e67cee215e196136e9a Mon Sep 17 00:00:00 2001 From: Paul Valladares <85648028+dreyfus92@users.noreply.github.com> Date: Mon, 9 Mar 2026 17:45:08 -0500 Subject: [PATCH 2/2] refactor(prompts): use parent listing instead of unshift for directory path selection --- packages/prompts/src/path.ts | 19 ++++++++----------- packages/prompts/test/path.test.ts | 4 ++-- 2 files changed, 10 insertions(+), 13 deletions(-) diff --git a/packages/prompts/src/path.ts b/packages/prompts/src/path.ts index 8c3615ee..abdf7f26 100644 --- a/packages/prompts/src/path.ts +++ b/packages/prompts/src/path.ts @@ -18,6 +18,7 @@ export const path = (opts: PathOptions) => { ...opts, initialUserInput: opts.initialValue ?? opts.root ?? process.cwd(), maxItems: 5, + filter: () => true, validate(value) { if (Array.isArray(value)) { // Shouldn't ever happen since we don't enable `multiple: true` @@ -39,20 +40,22 @@ export const path = (opts: PathOptions) => { try { let searchPath: string; - let userInputIsDirectory = false; if (!existsSync(userInput)) { searchPath = dirname(userInput); } else { const stat = lstatSync(userInput); - if (stat.isDirectory()) { - userInputIsDirectory = true; + if (stat.isDirectory() && !opts.directory) { searchPath = userInput; } else { searchPath = dirname(userInput); } } + // Strip trailing slash so startsWith matches the directory itself among its siblings + const prefix = + userInput.length > 1 && userInput.endsWith('/') ? userInput.slice(0, -1) : userInput; + const items = readdirSync(searchPath) .map((item) => { const path = join(searchPath, item); @@ -65,15 +68,9 @@ export const path = (opts: PathOptions) => { }) .filter( ({ path, isDirectory }) => - path.startsWith(userInput) && (isDirectory || !opts.directory) + path.startsWith(prefix) && (isDirectory || !opts.directory) ); - if (opts.directory && userInputIsDirectory) { - items.unshift({ - name: userInput, - path: userInput, - isDirectory: true, - }); - } + return items.map((item) => ({ value: item.path, })); diff --git a/packages/prompts/test/path.test.ts b/packages/prompts/test/path.test.ts index 86e69e96..90c4ef41 100644 --- a/packages/prompts/test/path.test.ts +++ b/packages/prompts/test/path.test.ts @@ -177,7 +177,7 @@ describe.each(['true', 'false'])('text (isCI = %s)', (isCI) => { const value = await result; - expect(value).toBe('/tmp/'); + expect(value).toBe('/tmp'); }); test('directory mode can navigate from initial directory to child directory', async () => { @@ -190,7 +190,7 @@ describe.each(['true', 'false'])('text (isCI = %s)', (isCI) => { output, }); - input.emit('keypress', '', { name: 'down' }); + input.emit('keypress', 'f', { name: 'f' }); input.emit('keypress', '', { name: 'return' }); const value = await result;