Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: bombshell-dev/clack
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: @clack/prompts@0.10.0
Choose a base ref
...
head repository: bombshell-dev/clack
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: main
Choose a head ref
  • 15 commits
  • 32 files changed
  • 7 contributors

Commits on Feb 5, 2025

  1. [ci] format

    natemoo-re authored and github-actions[bot] committed Feb 5, 2025
    Copy the full SHA
    d6d9ce7 View commit details

Commits on Feb 25, 2025

  1. Copy the full SHA
    5529c89 View commit details

Commits on Mar 18, 2025

  1. Copy the full SHA
    4d74348 View commit details
  2. chore: v1-alpha (#250)

    natemoo-re authored Mar 18, 2025
    Copy the full SHA
    a818595 View commit details
  3. feat!: move to esm-only (#246)

    Co-authored-by: Nate Moore <nate@natemoo.re>
    43081j and natemoo-re authored Mar 18, 2025
    Copy the full SHA
    c713fd5 View commit details
  4. chore: update actions

    natemoo-re committed Mar 18, 2025
    Copy the full SHA
    f9db1bd View commit details

Commits on Mar 21, 2025

  1. Copy the full SHA
    a36292b View commit details

Commits on Mar 29, 2025

  1. Copy the full SHA
    9cf1cc3 View commit details
  2. Copy the full SHA
    d8a10da View commit details

Commits on Mar 30, 2025

  1. feat: add selectableGroups option to group multi-select (#255)

    Co-authored-by: Nate Moore <nate@natemoo.re>
    43081j and natemoo-re authored Mar 30, 2025
    Copy the full SHA
    6868c1c View commit details
  2. test: add TextPrompt tests (#257)

    43081j authored Mar 30, 2025
    Copy the full SHA
    2a3755a View commit details
  3. Copy the full SHA
    a87628c View commit details
  4. Copy the full SHA
    eed2780 View commit details
  5. Copy the full SHA
    f87e751 View commit details
  6. Copy the full SHA
    041e13c View commit details
6 changes: 6 additions & 0 deletions .changeset/free-wasps-decide.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@clack/prompts": patch
"@clack/core": patch
---

Adds a new `selectableGroups` boolean to the group multi-select prompt. Using `selectableGroups: false` will disable the ability to select a top-level group, but still allow every child to be selected individually.
8 changes: 8 additions & 0 deletions .changeset/legal-bags-tie.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
"@clack/prompts": major
"@clack/core": major
---

The package is now distributed as ESM-only. In `v0` releases, the package was dual-published as CJS and ESM.

For existing CJS projects using Node v20+, please see Node's guide on [Loading ECMAScript modules using `require()`](https://nodejs.org/docs/latest-v20.x/api/modules.html#loading-ecmascript-modules-using-require).
5 changes: 5 additions & 0 deletions .changeset/lemon-monkeys-help.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@clack/core": patch
---

Fix "TTY initialization failed: uv_tty_init returned EBADF (bad file descriptor)" error happening on Windows for non-tty terminals.
11 changes: 11 additions & 0 deletions .changeset/pre.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"mode": "pre",
"tag": "alpha",
"initialVersions": {
"@example/basic": "0.0.0",
"@example/changesets": "0.0.0",
"@clack/core": "0.4.1",
"@clack/prompts": "0.10.0"
},
"changesets": []
}
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -26,5 +26,6 @@ jobs:
node-version: 20
cache: "pnpm"
- run: pnpm install
- run: pnpm run build
- run: pnpm run type-check
- run: pnpm run test
3 changes: 2 additions & 1 deletion .github/workflows/lint.yml
Original file line number Diff line number Diff line change
@@ -18,4 +18,5 @@ jobs:
node-version: 20
cache: "pnpm"
- run: pnpm install
- run: pnpm type-check
- run: pnpm run build
- run: pnpm run type-check
1 change: 1 addition & 0 deletions .github/workflows/preview.yml
Original file line number Diff line number Diff line change
@@ -3,6 +3,7 @@ on: [push, pull_request]

jobs:
build:
if: github.repository == 'bombshell-dev/clack'
runs-on: ubuntu-latest

steps:
3 changes: 1 addition & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -2,8 +2,7 @@ name: Release

on:
push:
branches:
- main
branches: [main, v0]

permissions:
contents: write
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -12,6 +12,9 @@
<h4 align="center"><a href="packages/core#readme"><code>@clack/core</code></a>: unstyled, extensible primitives for CLIs</h4>
<h4 align="center"><a href="packages/prompts#readme"><code>@clack/prompts</code></a>: beautiful, ready-to-use CLI prompt components</h4>

> [!WARNING]
> Clack's `main` branch is tracking the [`alpha` release line for `v1.0.0+`](https://github.com/bombshell-dev/clack/pull/250). To view the relatively stable `v0` line, please browse the [v0](https://github.com/bombshell-dev/clack/tree/v0) branch.
<br />
<br />

4 changes: 2 additions & 2 deletions build.preset.ts
Original file line number Diff line number Diff line change
@@ -3,10 +3,10 @@ import { definePreset } from 'unbuild';
// @see https://github.com/unjs/unbuild
export default definePreset({
clean: true,
declaration: true,
declaration: 'node16',
sourcemap: true,
rollup: {
emitCJS: true,
emitCJS: false,
inlineDependencies: true,
esbuild: {
minify: true,
2 changes: 1 addition & 1 deletion examples/basic/spinner-ci.ts
Original file line number Diff line number Diff line change
@@ -7,7 +7,7 @@
* - There will be no loading dots animation, instead it will be always `...`
* - Instead of erase the previous message, action that is blocked during CI, it will just write a new one.
*
* Issue: https://github.com/natemoo-re/clack/issues/168
* Issue: https://github.com/bombshell-dev/clack/issues/168
*/
import * as p from '@clack/prompts';

15 changes: 7 additions & 8 deletions packages/core/package.json
Original file line number Diff line number Diff line change
@@ -2,26 +2,25 @@
"name": "@clack/core",
"version": "0.4.1",
"type": "module",
"main": "./dist/index.cjs",
"main": "./dist/index.mjs",
"module": "./dist/index.mjs",
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.mjs",
"require": "./dist/index.cjs"
"types": "./dist/index.d.mts",
"default": "./dist/index.mjs"
},
"./package.json": "./package.json"
},
"types": "./dist/index.d.ts",
"types": "./dist/index.d.mts",
"repository": {
"type": "git",
"url": "https://github.com/natemoo-re/clack",
"url": "git+https://github.com/bombshell-dev/clack.git",
"directory": "packages/core"
},
"bugs": {
"url": "https://github.com/natemoo-re/clack/issues"
"url": "https://github.com/bombshell-dev/clack/issues"
},
"homepage": "https://github.com/natemoo-re/clack/tree/main/packages/core#readme",
"homepage": "https://github.com/bombshell-dev/clack/tree/main/packages/core#readme",
"files": ["dist", "CHANGELOG.md"],
"keywords": [
"ask",
24 changes: 12 additions & 12 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
export type { ClackState as State } from './types';
export type { ClackSettings } from './utils/settings';
export type { ClackState as State } from './types.js';
export type { ClackSettings } from './utils/settings.js';

export { default as ConfirmPrompt } from './prompts/confirm';
export { default as GroupMultiSelectPrompt } from './prompts/group-multiselect';
export { default as MultiSelectPrompt } from './prompts/multi-select';
export { default as PasswordPrompt } from './prompts/password';
export { default as Prompt } from './prompts/prompt';
export { default as SelectPrompt } from './prompts/select';
export { default as SelectKeyPrompt } from './prompts/select-key';
export { default as TextPrompt } from './prompts/text';
export { block, isCancel } from './utils';
export { updateSettings } from './utils/settings';
export { default as ConfirmPrompt } from './prompts/confirm.js';
export { default as GroupMultiSelectPrompt } from './prompts/group-multiselect.js';
export { default as MultiSelectPrompt } from './prompts/multi-select.js';
export { default as PasswordPrompt } from './prompts/password.js';
export { default as Prompt } from './prompts/prompt.js';
export { default as SelectPrompt } from './prompts/select.js';
export { default as SelectKeyPrompt } from './prompts/select-key.js';
export { default as TextPrompt } from './prompts/text.js';
export { block, isCancel } from './utils/index.js';
export { updateSettings } from './utils/settings.js';
2 changes: 1 addition & 1 deletion packages/core/src/prompts/confirm.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { cursor } from 'sisteransi';
import Prompt, { type PromptOptions } from './prompt';
import Prompt, { type PromptOptions } from './prompt.js';

interface ConfirmOptions extends PromptOptions<ConfirmPrompt> {
active: string;
21 changes: 17 additions & 4 deletions packages/core/src/prompts/group-multiselect.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
import Prompt, { type PromptOptions } from './prompt';
import Prompt, { type PromptOptions } from './prompt.js';

interface GroupMultiSelectOptions<T extends { value: any }>
extends PromptOptions<GroupMultiSelectPrompt<T>> {
options: Record<string, T[]>;
initialValues?: T['value'][];
required?: boolean;
cursorAt?: T['value'];
selectableGroups?: boolean;
}
export default class GroupMultiSelectPrompt<T extends { value: any }> extends Prompt {
options: (T & { group: string | boolean })[];
cursor = 0;
#selectableGroups: boolean;

getGroupItems(group: string): T[] {
return this.options.filter((o) => o.group === group);
@@ -44,26 +46,37 @@ export default class GroupMultiSelectPrompt<T extends { value: any }> extends Pr
constructor(opts: GroupMultiSelectOptions<T>) {
super(opts, false);
const { options } = opts;
this.#selectableGroups = opts.selectableGroups !== false;
this.options = Object.entries(options).flatMap(([key, option]) => [
{ value: key, group: true, label: key },
...option.map((opt) => ({ ...opt, group: key })),
]) as any;
this.value = [...(opts.initialValues ?? [])];
this.cursor = Math.max(
this.options.findIndex(({ value }) => value === opts.cursorAt),
0
this.#selectableGroups ? 0 : 1
);

this.on('cursor', (key) => {
switch (key) {
case 'left':
case 'up':
case 'up': {
this.cursor = this.cursor === 0 ? this.options.length - 1 : this.cursor - 1;
const currentIsGroup = this.options[this.cursor]?.group === true;
if (!this.#selectableGroups && currentIsGroup) {
this.cursor = this.cursor === 0 ? this.options.length - 1 : this.cursor - 1;
}
break;
}
case 'down':
case 'right':
case 'right': {
this.cursor = this.cursor === this.options.length - 1 ? 0 : this.cursor + 1;
const currentIsGroup = this.options[this.cursor]?.group === true;
if (!this.#selectableGroups && currentIsGroup) {
this.cursor = this.cursor === this.options.length - 1 ? 0 : this.cursor + 1;
}
break;
}
case 'space':
this.toggleValue();
break;
2 changes: 1 addition & 1 deletion packages/core/src/prompts/multi-select.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import Prompt, { type PromptOptions } from './prompt';
import Prompt, { type PromptOptions } from './prompt.js';

interface MultiSelectOptions<T extends { value: any }> extends PromptOptions<MultiSelectPrompt<T>> {
options: T[];
2 changes: 1 addition & 1 deletion packages/core/src/prompts/password.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import color from 'picocolors';
import Prompt, { type PromptOptions } from './prompt';
import Prompt, { type PromptOptions } from './prompt.js';

interface PasswordOptions extends PromptOptions<PasswordPrompt> {
mask?: string;
13 changes: 7 additions & 6 deletions packages/core/src/prompts/prompt.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { stdin, stdout } from 'node:process';
import readline, { type Key, type ReadLine } from 'node:readline';
import type { Readable, Writable } from 'node:stream';
import { WriteStream } from 'node:tty';
import type { Readable } from 'node:stream';
import { Writable } from 'node:stream';
import { cursor, erase } from 'sisteransi';
import wrap from 'wrap-ansi';

import { CANCEL_SYMBOL, diffLines, isActionKey, setRawMode, settings } from '../utils';
import { CANCEL_SYMBOL, diffLines, isActionKey, setRawMode, settings } from '../utils/index.js';

import type { ClackEvents, ClackState } from '../types';
import type { Action } from '../utils';
import type { ClackEvents, ClackState } from '../types.js';
import type { Action } from '../utils/index.js';

export interface PromptOptions<Self extends Prompt> {
render(this: Omit<Self, 'prompt'>): string | undefined;
@@ -133,7 +133,7 @@ export default class Prompt {
);
}

const sink = new WriteStream(0);
const sink = new Writable();
sink._write = (chunk, encoding, done) => {
if (this._track) {
this.value = this.rl?.line.replace(/\t/g, '');
@@ -150,6 +150,7 @@ export default class Prompt {
tabSize: 2,
prompt: '',
escapeCodeTimeout: 50,
terminal: true,
});
readline.emitKeypressEvents(this.input, this.rl);
this.rl.prompt();
2 changes: 1 addition & 1 deletion packages/core/src/prompts/select-key.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import Prompt, { type PromptOptions } from './prompt';
import Prompt, { type PromptOptions } from './prompt.js';

interface SelectKeyOptions<T extends { value: any }> extends PromptOptions<SelectKeyPrompt<T>> {
options: T[];
2 changes: 1 addition & 1 deletion packages/core/src/prompts/select.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import Prompt, { type PromptOptions } from './prompt';
import Prompt, { type PromptOptions } from './prompt.js';

interface SelectOptions<T extends { value: any }> extends PromptOptions<SelectPrompt<T>> {
options: T[];
2 changes: 1 addition & 1 deletion packages/core/src/prompts/text.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import color from 'picocolors';
import Prompt, { type PromptOptions } from './prompt';
import Prompt, { type PromptOptions } from './prompt.js';

export interface TextOptions extends PromptOptions<TextPrompt> {
placeholder?: string;
2 changes: 1 addition & 1 deletion packages/core/src/types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { Action } from './utils/settings';
import type { Action } from './utils/settings.js';

/**
* The state of the prompt
8 changes: 4 additions & 4 deletions packages/core/src/utils/index.ts
Original file line number Diff line number Diff line change
@@ -3,10 +3,10 @@ import type { Key } from 'node:readline';
import * as readline from 'node:readline';
import type { Readable } from 'node:stream';
import { cursor } from 'sisteransi';
import { isActionKey } from './settings';
import { isActionKey } from './settings.js';

export * from './string';
export * from './settings';
export * from './string.js';
export * from './settings.js';

const isWindows = globalThis.process.platform.startsWith('win');

@@ -61,7 +61,7 @@ export function block({
input.off('keypress', clear);
if (hideCursor) output.write(cursor.show);

// Prevent Windows specific issues: https://github.com/natemoo-re/clack/issues/176
// Prevent Windows specific issues: https://github.com/bombshell-dev/clack/issues/176
if (input.isTTY && !isWindows) input.setRawMode(false);

// @ts-expect-error fix for https://github.com/nodejs/node/issues/31762#issuecomment-1441223907
Loading