Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
5 changes: 5 additions & 0 deletions .changeset/path-windows-separator.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@clack/prompts": patch
---

Fix the `path` prompt in `directory` mode on Windows: typing a trailing `\` after a directory now lists its children instead of showing no matches.
8 changes: 5 additions & 3 deletions packages/prompts/src/path.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,21 +87,23 @@ export const path = (opts: PathOptions) => {

try {
let searchPath: string;
// Accept both separators on every OS: Windows users type `\`
const hasTrailingSeparator = userInput.endsWith('/') || userInput.endsWith('\\');

if (!existsSync(userInput)) {
searchPath = dirname(userInput);
} else {
const stat = lstatSync(userInput);
if (stat.isDirectory() && (!opts.directory || userInput.endsWith('/'))) {
if (stat.isDirectory() && (!opts.directory || hasTrailingSeparator)) {
searchPath = userInput;
} else {
searchPath = dirname(userInput);
}
}

// Strip trailing slash so startsWith matches the directory itself among its siblings
// Strip the trailing separator so startsWith matches the directory itself among its siblings
const prefix =
userInput.length > 1 && userInput.endsWith('/') ? userInput.slice(0, -1) : userInput;
userInput.length > 1 && hasTrailingSeparator ? userInput.slice(0, -1) : userInput;

const items = readdirSync(searchPath)
.map((item) => {
Expand Down
39 changes: 39 additions & 0 deletions packages/prompts/test/path.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,45 @@ describe.each(['true', 'false'])('text (isCI = %s)', (isCI) => {
expect(value).toBe('/tmp/foo');
});

// Windows path semantics: node:path is win32 there, and memfs maps C:\\tmp to /tmp.
test.skipIf(process.platform !== 'win32')(
'directory mode traverses into child when trailing backslash entered',
async () => {
const result = prompts.path({
message: 'foo',
initialValue: 'C:\\tmp',
directory: true,
input,
output,
});

input.emit('keypress', '\\', { name: '\\' });
input.emit('keypress', '', { name: 'return' });
// cancel instead of hanging if return did not submit
input.emit('keypress', '', { name: 'escape' });

expect(await result).toBe('C:\\tmp\\foo');
}
);

test.skipIf(process.platform !== 'win32')(
'directory mode lists children of an initial value ending in a backslash',
async () => {
const result = prompts.path({
message: 'foo',
initialValue: 'C:\\tmp\\',
directory: true,
input,
output,
});

input.emit('keypress', '', { name: 'return' });
input.emit('keypress', '', { name: 'escape' });

expect(await result).toBe('C:\\tmp\\foo');
}
);

test('directory mode can navigate from initial directory to child directory', async () => {
const result = prompts.path({
message: 'foo',
Expand Down
Loading