Skip to content
Draft
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
16 changes: 16 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<!-- intent-skills:start -->
# Skill mappings - when working in these areas, load the linked skill file into context.
skills:
- task: "Building packages for publish or generating declarations"
load: "node_modules/@bomb.sh/tools/skills/build/SKILL.md"
- task: "Writing or running tests"
load: "node_modules/@bomb.sh/tools/skills/test/SKILL.md"
- task: "Development workflow, command ordering, monorepo filtering"
load: "node_modules/@bomb.sh/tools/skills/lifecycle/SKILL.md"
- task: "Linting, code quality, or understanding lint violations"
load: "node_modules/@bomb.sh/tools/skills/lint/SKILL.md"
- task: "Formatting source files before committing"
load: "node_modules/@bomb.sh/tools/skills/format/SKILL.md"
- task: "Migrating off biome, unbuild, or other tools to bsh toolchain"
load: "node_modules/@bomb.sh/tools/skills/migrate/SKILL.md"
<!-- intent-skills:end -->
47 changes: 0 additions & 47 deletions biome.json

This file was deleted.

15 changes: 0 additions & 15 deletions build.preset.ts

This file was deleted.

13 changes: 4 additions & 9 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,20 @@
"private": true,
"type": "module",
"scripts": {
"stub": "pnpm -r run build --stub",
"build": "pnpm --filter \"@clack/*\" run build",
"start": "pnpm run dev",
"dev": "pnpm --filter @example/changesets run start",
"format": "biome check --write",
"lint": "biome lint --write --unsafe",
"types": "tsc --noEmit",
"deps": "pnpm exec knip --production",
"format": "bsh format",
"lint": "pnpm -r run lint",
"test": "pnpm --color -r run test",
"pretest": "pnpm run build"
},
"devDependencies": {
"@biomejs/biome": "^2.1.2",
"@bomb.sh/tools": "^0.3.4",
"@changesets/cli": "^2.29.5",
"@types/node": "^24.1.0",
"jsr": "^0.13.4",
"knip": "^5.62.0",
"typescript": "^5.8.3",
"unbuild": "^3.6.0"
"typescript": "^5.8.3"
},
"packageManager": "pnpm@9.14.2",
"volta": {
Expand Down
7 changes: 0 additions & 7 deletions packages/core/build.config.ts

This file was deleted.

8 changes: 3 additions & 5 deletions packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,15 +48,13 @@
"license": "MIT",
"packageManager": "pnpm@9.14.2",
"scripts": {
"build": "unbuild",
"build": "bsh build \"src/**/*.ts\" \"!src/**/*.test.ts\"",
"prepack": "pnpm build",
"test": "vitest run"
"test": "bsh test",
"lint": "bsh lint"
},
"dependencies": {
"fast-wrap-ansi": "^0.1.3",
"sisteransi": "^1.0.5"
},
"devDependencies": {
"vitest": "^3.2.4"
}
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
import { cursor } from 'sisteransi';
import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest';
import { default as AutocompletePrompt } from '../../src/prompts/autocomplete.js';
import { MockReadable } from '../mock-readable.js';
import { MockWritable } from '../mock-writable.js';
import { beforeEach, describe, expect, test } from 'vitest';
import { default as AutocompletePrompt } from './autocomplete.js';
import { createMocks, type Mocks } from '@bomb.sh/tools/test-utils';

describe('AutocompletePrompt', () => {
let input: MockReadable;
let output: MockWritable;
let mocks: Mocks<{ input: true; output: true }>;
const testOptions = [
{ value: 'apple', label: 'Apple' },
{ value: 'banana', label: 'Banana' },
Expand All @@ -16,29 +14,24 @@ describe('AutocompletePrompt', () => {
];

beforeEach(() => {
input = new MockReadable();
output = new MockWritable();
});

afterEach(() => {
vi.restoreAllMocks();
mocks = createMocks({ input: true, output: true });
});

test('renders render() result', () => {
const instance = new AutocompletePrompt({
input,
output,
input: mocks.input,
output: mocks.output,
render: () => 'foo',
options: testOptions,
});
instance.prompt();
expect(output.buffer).to.deep.equal([cursor.hide, 'foo']);
expect(mocks.output.buffer).to.deep.equal([cursor.hide, 'foo']);
});

test('initial options match provided options', () => {
const instance = new AutocompletePrompt({
input,
output,
input: mocks.input,
output: mocks.output,
render: () => 'foo',
options: testOptions,
});
Expand All @@ -52,8 +45,8 @@ describe('AutocompletePrompt', () => {

test('cursor navigation with event emitter', () => {
const instance = new AutocompletePrompt({
input,
output,
input: mocks.input,
output: mocks.output,
render: () => 'foo',
options: testOptions,
});
Expand All @@ -78,8 +71,8 @@ describe('AutocompletePrompt', () => {

test('initialValue selects correct option', () => {
const instance = new AutocompletePrompt({
input,
output,
input: mocks.input,
output: mocks.output,
render: () => 'foo',
options: testOptions,
initialValue: ['cherry'],
Expand All @@ -95,8 +88,8 @@ describe('AutocompletePrompt', () => {

test('initialValue defaults to first option when non-multiple', () => {
const instance = new AutocompletePrompt({
input,
output,
input: mocks.input,
output: mocks.output,
render: () => 'foo',
options: testOptions,
});
Expand All @@ -107,8 +100,8 @@ describe('AutocompletePrompt', () => {

test('initialValue is empty when multiple', () => {
const instance = new AutocompletePrompt({
input,
output,
input: mocks.input,
output: mocks.output,
render: () => 'foo',
options: testOptions,
multiple: true,
Expand All @@ -120,8 +113,8 @@ describe('AutocompletePrompt', () => {

test('filtering through user input', () => {
const instance = new AutocompletePrompt({
input,
output,
input: mocks.input,
output: mocks.output,
render: () => 'foo',
options: testOptions,
});
Expand All @@ -132,7 +125,7 @@ describe('AutocompletePrompt', () => {
expect(instance.filteredOptions.length).to.equal(testOptions.length);

// Simulate typing 'a' by emitting keypress event
input.emit('keypress', 'a', { name: 'a' });
mocks.input.emit('keypress', 'a', { name: 'a' });

// Check that filtered options are updated to include options with 'a'
expect(instance.filteredOptions.length).to.be.lessThan(testOptions.length);
Expand All @@ -144,37 +137,37 @@ describe('AutocompletePrompt', () => {

test('default filter function works correctly', () => {
const instance = new AutocompletePrompt({
input,
output,
input: mocks.input,
output: mocks.output,
render: () => 'foo',
options: testOptions,
});

instance.prompt();

input.emit('keypress', 'a', { name: 'a' });
input.emit('keypress', 'p', { name: 'p' });
mocks.input.emit('keypress', 'a', { name: 'a' });
mocks.input.emit('keypress', 'p', { name: 'p' });

expect(instance.filteredOptions).toEqual([
{ value: 'apple', label: 'Apple' },
{ value: 'grape', label: 'Grape' },
]);

input.emit('keypress', 'z', { name: 'z' });
mocks.input.emit('keypress', 'z', { name: 'z' });

expect(instance.filteredOptions).toEqual([]);
});

test('submit without nav resolves to first option in non-multiple', async () => {
const instance = new AutocompletePrompt({
input,
output,
input: mocks.input,
output: mocks.output,
render: () => 'foo',
options: testOptions,
});

const promise = instance.prompt();
input.emit('keypress', '', { name: 'return' });
mocks.input.emit('keypress', '', { name: 'return' });
const result = await promise;

expect(instance.selectedValues).to.deep.equal(['apple']);
Expand All @@ -183,15 +176,15 @@ describe('AutocompletePrompt', () => {

test('submit without nav resolves to [] in multiple', async () => {
const instance = new AutocompletePrompt({
input,
output,
input: mocks.input,
output: mocks.output,
render: () => 'foo',
options: testOptions,
multiple: true,
});

const promise = instance.prompt();
input.emit('keypress', '', { name: 'return' });
mocks.input.emit('keypress', '', { name: 'return' });
const result = await promise;

expect(instance.selectedValues).to.deep.equal([]);
Expand All @@ -200,16 +193,16 @@ describe('AutocompletePrompt', () => {

test('Tab with empty input and placeholder fills input and submit returns matching option', async () => {
const instance = new AutocompletePrompt({
input,
output,
input: mocks.input,
output: mocks.output,
render: () => 'foo',
options: testOptions,
placeholder: 'apple',
});

const promise = instance.prompt();
input.emit('keypress', '\t', { name: 'tab' });
input.emit('keypress', '', { name: 'return' });
mocks.input.emit('keypress', '\t', { name: 'tab' });
mocks.input.emit('keypress', '', { name: 'return' });
const result = await promise;

expect(instance.userInput).to.equal('apple');
Expand All @@ -218,15 +211,15 @@ describe('AutocompletePrompt', () => {

test('Tab with non-matching placeholder does not fill input', async () => {
const instance = new AutocompletePrompt({
input,
output,
input: mocks.input,
output: mocks.output,
render: () => 'foo',
options: testOptions,
placeholder: 'Type to search...',
});

instance.prompt();
input.emit('keypress', '\t', { name: 'tab' });
mocks.input.emit('keypress', '\t', { name: 'tab' });

// Placeholder does not match any option, so input must not be filled with placeholder
expect(instance.userInput).not.to.equal('Type to search...');
Expand Down
Loading
Loading