From 9312c0876b8a3622b87eaf733058df3c1291ba42 Mon Sep 17 00:00:00 2001
From: James Garbutt <43081j@users.noreply.github.com>
Date: Tue, 8 Jul 2025 14:04:49 +0100
Subject: [PATCH 1/8] wip: implement JSX package
---
examples/basic/spinner-cancel-advanced.ts | 4 +-
examples/basic/text-validation.ts | 4 +-
package.json | 1 +
packages/core/tsconfig.json | 3 +-
packages/jsx/CHANGELOG.md | 2 +
packages/jsx/LICENSE | 9 ++++
packages/jsx/README.md | 18 +++++++
packages/jsx/build.config.ts | 7 +++
packages/jsx/jsx-runtime.d.ts | 1 +
packages/jsx/jsx-runtime.js | 1 +
packages/jsx/package.json | 62 +++++++++++++++++++++++
packages/jsx/src/index.ts | 25 +++++++++
packages/jsx/test/jsx.test.tsx | 27 ++++++++++
packages/jsx/tsconfig.json | 8 +++
packages/prompts/test/password.test.ts | 2 +-
packages/prompts/tsconfig.json | 3 +-
packages/test-utils/LICENSE | 9 ++++
packages/test-utils/README.md | 3 ++
packages/test-utils/package.json | 40 +++++++++++++++
packages/test-utils/src/index.ts | 2 +
packages/test-utils/src/mock-readable.ts | 26 ++++++++++
packages/test-utils/src/mock-writable.ts | 14 +++++
packages/test-utils/tsconfig.json | 9 ++++
pnpm-lock.yaml | 15 ++++++
tsconfig.json | 3 +-
25 files changed, 289 insertions(+), 9 deletions(-)
create mode 100644 packages/jsx/CHANGELOG.md
create mode 100644 packages/jsx/LICENSE
create mode 100644 packages/jsx/README.md
create mode 100644 packages/jsx/build.config.ts
create mode 100644 packages/jsx/jsx-runtime.d.ts
create mode 100644 packages/jsx/jsx-runtime.js
create mode 100644 packages/jsx/package.json
create mode 100644 packages/jsx/src/index.ts
create mode 100644 packages/jsx/test/jsx.test.tsx
create mode 100644 packages/jsx/tsconfig.json
create mode 100644 packages/test-utils/LICENSE
create mode 100644 packages/test-utils/README.md
create mode 100644 packages/test-utils/package.json
create mode 100644 packages/test-utils/src/index.ts
create mode 100644 packages/test-utils/src/mock-readable.ts
create mode 100644 packages/test-utils/src/mock-writable.ts
create mode 100644 packages/test-utils/tsconfig.json
diff --git a/examples/basic/spinner-cancel-advanced.ts b/examples/basic/spinner-cancel-advanced.ts
index cbf0927d..6cb9a52a 100644
--- a/examples/basic/spinner-cancel-advanced.ts
+++ b/examples/basic/spinner-cancel-advanced.ts
@@ -81,7 +81,7 @@ async function main() {
} catch (error) {
// Handle errors but continue if not cancelled
if (!processSpinner.isCancelled) {
- p.note(`Error processing ${language}: ${error.message}`, 'Error');
+ p.note(`Error processing ${language}: ${(error as Error).message}`, 'Error');
}
}
}
@@ -134,7 +134,7 @@ async function main() {
}
} catch (error) {
if (!finalSpinner.isCancelled) {
- finalSpinner.stop(`Error during ${action}: ${error.message}`);
+ finalSpinner.stop(`Error during ${action}: ${(error as Error).message}`);
}
}
}
diff --git a/examples/basic/text-validation.ts b/examples/basic/text-validation.ts
index 9a637c68..7b928e4c 100644
--- a/examples/basic/text-validation.ts
+++ b/examples/basic/text-validation.ts
@@ -9,7 +9,7 @@ async function main() {
message: 'Enter your name (letters and spaces only)',
initialValue: 'John123', // Invalid initial value with numbers
validate: (value) => {
- if (!/^[a-zA-Z\s]+$/.test(value)) return 'Name can only contain letters and spaces';
+ if (!value || !/^[a-zA-Z\s]+$/.test(value)) return 'Name can only contain letters and spaces';
return undefined;
},
});
@@ -25,7 +25,7 @@ async function main() {
message: 'Enter another name (letters and spaces only)',
initialValue: 'John Doe', // Valid initial value
validate: (value) => {
- if (!/^[a-zA-Z\s]+$/.test(value)) return 'Name can only contain letters and spaces';
+ if (!value || !/^[a-zA-Z\s]+$/.test(value)) return 'Name can only contain letters and spaces';
return undefined;
},
});
diff --git a/package.json b/package.json
index 39950050..30bfcd19 100644
--- a/package.json
+++ b/package.json
@@ -9,6 +9,7 @@
"dev": "pnpm --filter @example/changesets run start",
"format": "biome check --write",
"lint": "biome lint --write --unsafe",
+ "typecheck": "pnpm -r exec tsc --noEmit",
"types": "biome lint --write --unsafe",
"deps": "pnpm exec knip --production",
"test": "pnpm --color -r run test",
diff --git a/packages/core/tsconfig.json b/packages/core/tsconfig.json
index 4082f16a..33e1aaf3 100644
--- a/packages/core/tsconfig.json
+++ b/packages/core/tsconfig.json
@@ -1,3 +1,4 @@
{
- "extends": "../../tsconfig.json"
+ "extends": "../../tsconfig.json",
+ "include": ["./src", "./test"]
}
diff --git a/packages/jsx/CHANGELOG.md b/packages/jsx/CHANGELOG.md
new file mode 100644
index 00000000..b3779c2a
--- /dev/null
+++ b/packages/jsx/CHANGELOG.md
@@ -0,0 +1,2 @@
+# @clack/core
+
diff --git a/packages/jsx/LICENSE b/packages/jsx/LICENSE
new file mode 100644
index 00000000..885bb86b
--- /dev/null
+++ b/packages/jsx/LICENSE
@@ -0,0 +1,9 @@
+MIT License
+
+Copyright (c) Bombshell Maintainers
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/packages/jsx/README.md b/packages/jsx/README.md
new file mode 100644
index 00000000..a3c9be3b
--- /dev/null
+++ b/packages/jsx/README.md
@@ -0,0 +1,18 @@
+# `@clack/jsx`
+
+This package contains JSX support for clack, allowing you to define your
+prompts declaratively.
+
+Each `Prompt` can be rendered as if it were a component:
+
+```tsx
+import {Confirm} from '@clack/jsx';
+
+const p = ();
+
+const name = await p;
+
+if (isCancel(name)) {
+ process.exit(0);
+}
+```
diff --git a/packages/jsx/build.config.ts b/packages/jsx/build.config.ts
new file mode 100644
index 00000000..8bd1ad27
--- /dev/null
+++ b/packages/jsx/build.config.ts
@@ -0,0 +1,7 @@
+import { defineBuildConfig } from 'unbuild';
+
+// @see https://github.com/unjs/unbuild
+export default defineBuildConfig({
+ preset: '../../build.preset',
+ entries: ['src/index'],
+});
diff --git a/packages/jsx/jsx-runtime.d.ts b/packages/jsx/jsx-runtime.d.ts
new file mode 100644
index 00000000..28e95ed3
--- /dev/null
+++ b/packages/jsx/jsx-runtime.d.ts
@@ -0,0 +1 @@
+export { jsx, jsxDEV } from './dist/index.mjs';
diff --git a/packages/jsx/jsx-runtime.js b/packages/jsx/jsx-runtime.js
new file mode 100644
index 00000000..28e95ed3
--- /dev/null
+++ b/packages/jsx/jsx-runtime.js
@@ -0,0 +1 @@
+export { jsx, jsxDEV } from './dist/index.mjs';
diff --git a/packages/jsx/package.json b/packages/jsx/package.json
new file mode 100644
index 00000000..3e9018f9
--- /dev/null
+++ b/packages/jsx/package.json
@@ -0,0 +1,62 @@
+{
+ "name": "@clack/jsx",
+ "version": "1.0.0-alpha.1",
+ "type": "module",
+ "main": "./dist/index.mjs",
+ "module": "./dist/index.mjs",
+ "exports": {
+ ".": {
+ "types": "./dist/index.d.mts",
+ "default": "./dist/index.mjs"
+ },
+ "./jsx-runtime": "./jsx-runtime.js",
+ "./jsx-dev-runtime": "./jsx-runtime.js",
+ "./package.json": "./package.json"
+ },
+ "types": "./dist/index.d.mts",
+ "repository": {
+ "type": "git",
+ "url": "git+https://github.com/bombshell-dev/clack.git",
+ "directory": "packages/jsx"
+ },
+ "bugs": {
+ "url": "https://github.com/bombshell-dev/clack/issues"
+ },
+ "homepage": "https://github.com/bombshell-dev/clack/tree/main/packages/jsx#readme",
+ "files": ["dist", "CHANGELOG.md"],
+ "keywords": [
+ "ask",
+ "clack",
+ "cli",
+ "command-line",
+ "command",
+ "input",
+ "interact",
+ "interface",
+ "menu",
+ "prompt",
+ "prompts",
+ "stdin",
+ "ui",
+ "jsx",
+ "ink"
+ ],
+ "author": {
+ "name": "James Garbutt",
+ "url": "https://github.com/43081j"
+ },
+ "license": "MIT",
+ "packageManager": "pnpm@9.14.2",
+ "scripts": {
+ "build": "unbuild",
+ "prepack": "pnpm build",
+ "test": "vitest run"
+ },
+ "dependencies": {
+ "@clack/prompts": "workspace:*"
+ },
+ "devDependencies": {
+ "vitest": "^3.1.1",
+ "@clack/test-utils": "workspace:*"
+ }
+}
diff --git a/packages/jsx/src/index.ts b/packages/jsx/src/index.ts
new file mode 100644
index 00000000..f6434bf2
--- /dev/null
+++ b/packages/jsx/src/index.ts
@@ -0,0 +1,25 @@
+import type { ConfirmOptions } from '@clack/prompts';
+
+namespace JSX {
+ export interface IntrinsicElements {
+ confirm: ConfirmOptions;
+ }
+
+ export type Element = unknown;
+}
+
+export type { JSX };
+
+export function Confirm(_props: JSX.IntrinsicElements['confirm']): JSX.Element {
+ return 'foo';
+}
+
+export function jsx(
+ tag: T,
+ props: JSX.IntrinsicElements[T],
+ _key?: string
+): JSX.Element {
+ return 'foo';
+}
+
+export const jsxDEV = jsx;
diff --git a/packages/jsx/test/jsx.test.tsx b/packages/jsx/test/jsx.test.tsx
new file mode 100644
index 00000000..369692be
--- /dev/null
+++ b/packages/jsx/test/jsx.test.tsx
@@ -0,0 +1,27 @@
+import { MockReadable, MockWritable } from '@clack/test-utils';
+import { beforeEach, describe, expect, test } from 'vitest';
+import { Confirm, jsx } from '../src/index.js';
+
+describe('jsx', () => {
+ let input: MockReadable;
+ let output: MockWritable;
+
+ beforeEach(() => {
+ input = new MockReadable();
+ output = new MockWritable();
+ });
+
+ test('can render', async () => {
+ const result = jsx('confirm', {
+ message: 'foo?',
+ input,
+ output,
+ });
+ expect(result).to.equal('foo');
+ });
+
+ test('can render JSX', () => {
+ const result = ;
+ expect(result).to.equal('foo');
+ });
+});
diff --git a/packages/jsx/tsconfig.json b/packages/jsx/tsconfig.json
new file mode 100644
index 00000000..4d69f21b
--- /dev/null
+++ b/packages/jsx/tsconfig.json
@@ -0,0 +1,8 @@
+{
+ "extends": "../../tsconfig.json",
+ "compilerOptions": {
+ "jsx": "react-jsx",
+ "jsxImportSource": "@clack/jsx"
+ },
+ "include": ["./src", "./test"]
+}
diff --git a/packages/prompts/test/password.test.ts b/packages/prompts/test/password.test.ts
index 9c8c9b7e..3a5479d0 100644
--- a/packages/prompts/test/password.test.ts
+++ b/packages/prompts/test/password.test.ts
@@ -77,7 +77,7 @@ describe.each(['true', 'false'])('password (isCI = %s)', (isCI) => {
const result = prompts.password({
message: 'foo',
validate: (value) => {
- if (value.length < 2) {
+ if (!value || value.length < 2) {
return 'Password must be at least 2 characters';
}
diff --git a/packages/prompts/tsconfig.json b/packages/prompts/tsconfig.json
index 4082f16a..33e1aaf3 100644
--- a/packages/prompts/tsconfig.json
+++ b/packages/prompts/tsconfig.json
@@ -1,3 +1,4 @@
{
- "extends": "../../tsconfig.json"
+ "extends": "../../tsconfig.json",
+ "include": ["./src", "./test"]
}
diff --git a/packages/test-utils/LICENSE b/packages/test-utils/LICENSE
new file mode 100644
index 00000000..885bb86b
--- /dev/null
+++ b/packages/test-utils/LICENSE
@@ -0,0 +1,9 @@
+MIT License
+
+Copyright (c) Bombshell Maintainers
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/packages/test-utils/README.md b/packages/test-utils/README.md
new file mode 100644
index 00000000..722756f8
--- /dev/null
+++ b/packages/test-utils/README.md
@@ -0,0 +1,3 @@
+# `@clack/test-utils`
+
+A bunch of useful test utiltiies for use inside clack.
diff --git a/packages/test-utils/package.json b/packages/test-utils/package.json
new file mode 100644
index 00000000..f422db68
--- /dev/null
+++ b/packages/test-utils/package.json
@@ -0,0 +1,40 @@
+{
+ "name": "@clack/test-utils",
+ "private": true,
+ "version": "1.0.0-alpha.1",
+ "type": "module",
+ "main": "./dist/index.js",
+ "module": "./dist/index.js",
+ "exports": {
+ ".": {
+ "types": "./dist/index.d.ts",
+ "default": "./dist/index.js"
+ },
+ "./package.json": "./package.json"
+ },
+ "types": "./dist/index.d.ts",
+ "repository": {
+ "type": "git",
+ "url": "git+https://github.com/bombshell-dev/clack.git",
+ "directory": "packages/test-utils"
+ },
+ "bugs": {
+ "url": "https://github.com/bombshell-dev/clack/issues"
+ },
+ "homepage": "https://github.com/bombshell-dev/clack/tree/main/packages/test-utils#readme",
+ "files": ["dist", "CHANGELOG.md"],
+ "author": {
+ "name": "James Garbutt",
+ "url": "https://github.com/43081j"
+ },
+ "license": "MIT",
+ "packageManager": "pnpm@9.14.2",
+ "scripts": {
+ "build": "tsc",
+ "prepack": "pnpm build"
+ },
+ "dependencies": {
+ },
+ "devDependencies": {
+ }
+}
diff --git a/packages/test-utils/src/index.ts b/packages/test-utils/src/index.ts
new file mode 100644
index 00000000..fda11f44
--- /dev/null
+++ b/packages/test-utils/src/index.ts
@@ -0,0 +1,2 @@
+export { MockReadable } from './mock-readable.js';
+export { MockWritable } from './mock-writable.js';
diff --git a/packages/test-utils/src/mock-readable.ts b/packages/test-utils/src/mock-readable.ts
new file mode 100644
index 00000000..b08e4879
--- /dev/null
+++ b/packages/test-utils/src/mock-readable.ts
@@ -0,0 +1,26 @@
+import { Readable } from 'node:stream';
+
+export class MockReadable extends Readable {
+ protected _buffer: unknown[] | null = [];
+
+ _read() {
+ if (this._buffer === null) {
+ this.push(null);
+ return;
+ }
+
+ for (const val of this._buffer) {
+ this.push(val);
+ }
+
+ this._buffer = [];
+ }
+
+ pushValue(val: unknown): void {
+ this._buffer?.push(val);
+ }
+
+ close(): void {
+ this._buffer = null;
+ }
+}
diff --git a/packages/test-utils/src/mock-writable.ts b/packages/test-utils/src/mock-writable.ts
new file mode 100644
index 00000000..746b0a0d
--- /dev/null
+++ b/packages/test-utils/src/mock-writable.ts
@@ -0,0 +1,14 @@
+import { Writable } from 'node:stream';
+
+export class MockWritable extends Writable {
+ public buffer: string[] = [];
+
+ _write(
+ chunk: any,
+ _encoding: BufferEncoding,
+ callback: (error?: Error | null | undefined) => void
+ ): void {
+ this.buffer.push(chunk.toString());
+ callback();
+ }
+}
diff --git a/packages/test-utils/tsconfig.json b/packages/test-utils/tsconfig.json
new file mode 100644
index 00000000..e04a918e
--- /dev/null
+++ b/packages/test-utils/tsconfig.json
@@ -0,0 +1,9 @@
+{
+ "extends": "../../tsconfig.json",
+ "compilerOptions": {
+ "noEmit": false,
+ "declaration": true,
+ "outDir": "./dist"
+ },
+ "include": ["./src"]
+}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index d2ae3cb1..37f7a585 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -74,6 +74,19 @@ importers:
specifier: ^8.1.0
version: 8.1.0
+ packages/jsx:
+ dependencies:
+ '@clack/prompts':
+ specifier: workspace:*
+ version: link:../prompts
+ devDependencies:
+ '@clack/test-utils':
+ specifier: workspace:*
+ version: link:../test-utils
+ vitest:
+ specifier: ^3.1.1
+ version: 3.1.1(@types/node@18.16.0)
+
packages/prompts:
dependencies:
'@clack/core':
@@ -99,6 +112,8 @@ importers:
specifier: ^0.1.2
version: 0.1.2(vitest@3.1.1(@types/node@18.16.0))
+ packages/test-utils: {}
+
packages:
'@ampproject/remapping@2.3.0':
diff --git a/tsconfig.json b/tsconfig.json
index 50008170..e9f4064f 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -15,6 +15,5 @@
"paths": {
"@clack/core": ["./packages/core/src"]
}
- },
- "include": ["packages"]
+ }
}
From eb79a94edf656b55f4bca09a30d788223aa80d72 Mon Sep 17 00:00:00 2001
From: James Garbutt <43081j@users.noreply.github.com>
Date: Tue, 8 Jul 2025 14:52:39 +0100
Subject: [PATCH 2/8] wip: add some tests
---
packages/jsx/src/index.ts | 29 +++++++++++++++++++++++------
packages/jsx/test/jsx.test.tsx | 20 +++++++++++++++-----
2 files changed, 38 insertions(+), 11 deletions(-)
diff --git a/packages/jsx/src/index.ts b/packages/jsx/src/index.ts
index f6434bf2..178da13f 100644
--- a/packages/jsx/src/index.ts
+++ b/packages/jsx/src/index.ts
@@ -1,25 +1,42 @@
import type { ConfirmOptions } from '@clack/prompts';
+import { confirm } from '@clack/prompts';
namespace JSX {
export interface IntrinsicElements {
- confirm: ConfirmOptions;
}
- export type Element = unknown;
+ export type Element = Promise;
}
export type { JSX };
-export function Confirm(_props: JSX.IntrinsicElements['confirm']): JSX.Element {
- return 'foo';
+export function Confirm(props: ConfirmOptions): ReturnType {
+ return confirm(props);
}
-export function jsx(
+export type Component =
+ | typeof Confirm;
+
+function jsx(
tag: T,
props: JSX.IntrinsicElements[T],
_key?: string
+): JSX.Element;
+function jsx(
+ fn: T,
+ props: Parameters[0],
+ _key?: string
+): JSX.Element;
+function jsx(
+ tagOrFn: string | Component,
+ props: unknown,
+ _key?: string
): JSX.Element {
- return 'foo';
+ if (typeof tagOrFn === 'function') {
+ return (tagOrFn as (props: unknown) => JSX.Element)(props);
+ }
+ return Promise.resolve(null);
}
+export { jsx };
export const jsxDEV = jsx;
diff --git a/packages/jsx/test/jsx.test.tsx b/packages/jsx/test/jsx.test.tsx
index 369692be..57d19aa2 100644
--- a/packages/jsx/test/jsx.test.tsx
+++ b/packages/jsx/test/jsx.test.tsx
@@ -12,16 +12,26 @@ describe('jsx', () => {
});
test('can render', async () => {
- const result = jsx('confirm', {
+ const task = jsx(Confirm, {
message: 'foo?',
input,
output,
});
- expect(result).to.equal('foo');
+ input.emit('keypress', '', { name: 'return' });
+ const result = await task;
+ expect(result).to.equal(true);
});
- test('can render JSX', () => {
- const result = ;
- expect(result).to.equal('foo');
+ test('can render JSX', async () => {
+ const task = ();
+ input.emit('keypress', '', { name: 'return' });
+ const result = await task;
+ expect(result).to.equal(true);
+ });
+
+ test('unknown elements are null', async () => {
+ const task = jsx('unknown-nonsense' as never, {} as never);
+ const result = await task;
+ expect(result).to.equal(null);
});
});
From c933bd915317d0132a5be79707de9a709e8e55c9 Mon Sep 17 00:00:00 2001
From: James Garbutt <43081j@users.noreply.github.com>
Date: Tue, 8 Jul 2025 17:41:03 +0100
Subject: [PATCH 3/8] wip: initial components
---
packages/jsx/jsx-runtime.d.ts | 2 +-
packages/jsx/jsx-runtime.js | 2 +-
packages/jsx/package.json | 1 +
packages/jsx/src/components/confirm.ts | 8 +
packages/jsx/src/components/note.ts | 31 ++
packages/jsx/src/components/option.ts | 31 ++
packages/jsx/src/components/password.ts | 8 +
packages/jsx/src/components/select.ts | 35 ++
packages/jsx/src/components/text.ts | 8 +
packages/jsx/src/index.ts | 53 +--
packages/jsx/src/types.ts | 7 +
packages/jsx/src/utils.ts | 16 +
.../jsx/test/__snapshots__/jsx.test.tsx.snap | 302 ++++++++++++++++++
packages/jsx/test/jsx.test.tsx | 175 +++++++++-
packages/jsx/vitest.config.ts | 7 +
pnpm-lock.yaml | 3 +
16 files changed, 663 insertions(+), 26 deletions(-)
create mode 100644 packages/jsx/src/components/confirm.ts
create mode 100644 packages/jsx/src/components/note.ts
create mode 100644 packages/jsx/src/components/option.ts
create mode 100644 packages/jsx/src/components/password.ts
create mode 100644 packages/jsx/src/components/select.ts
create mode 100644 packages/jsx/src/components/text.ts
create mode 100644 packages/jsx/src/types.ts
create mode 100644 packages/jsx/src/utils.ts
create mode 100644 packages/jsx/test/__snapshots__/jsx.test.tsx.snap
create mode 100644 packages/jsx/vitest.config.ts
diff --git a/packages/jsx/jsx-runtime.d.ts b/packages/jsx/jsx-runtime.d.ts
index 28e95ed3..98bc230b 100644
--- a/packages/jsx/jsx-runtime.d.ts
+++ b/packages/jsx/jsx-runtime.d.ts
@@ -1 +1 @@
-export { jsx, jsxDEV } from './dist/index.mjs';
+export * from './dist/index.mjs';
diff --git a/packages/jsx/jsx-runtime.js b/packages/jsx/jsx-runtime.js
index 28e95ed3..98bc230b 100644
--- a/packages/jsx/jsx-runtime.js
+++ b/packages/jsx/jsx-runtime.js
@@ -1 +1 @@
-export { jsx, jsxDEV } from './dist/index.mjs';
+export * from './dist/index.mjs';
diff --git a/packages/jsx/package.json b/packages/jsx/package.json
index 3e9018f9..bd6122b1 100644
--- a/packages/jsx/package.json
+++ b/packages/jsx/package.json
@@ -57,6 +57,7 @@
},
"devDependencies": {
"vitest": "^3.1.1",
+ "vitest-ansi-serializer": "^0.1.2",
"@clack/test-utils": "workspace:*"
}
}
diff --git a/packages/jsx/src/components/confirm.ts b/packages/jsx/src/components/confirm.ts
new file mode 100644
index 00000000..2293d80e
--- /dev/null
+++ b/packages/jsx/src/components/confirm.ts
@@ -0,0 +1,8 @@
+import type { ConfirmOptions } from '@clack/prompts';
+import { confirm } from '@clack/prompts';
+
+export type ConfirmProps = ConfirmOptions;
+
+export function Confirm(props: ConfirmProps): ReturnType {
+ return confirm(props);
+}
diff --git a/packages/jsx/src/components/note.ts b/packages/jsx/src/components/note.ts
new file mode 100644
index 00000000..94ac17de
--- /dev/null
+++ b/packages/jsx/src/components/note.ts
@@ -0,0 +1,31 @@
+import type { NoteOptions } from '@clack/prompts';
+import { isCancel, note } from '@clack/prompts';
+import type { JSX } from '../types.js';
+import { resolveChildren } from '../utils.js';
+
+export interface NoteProps extends NoteOptions {
+ children?: JSX.Element[] | JSX.Element | string;
+ message?: string;
+ title?: string;
+}
+
+export async function Note(props: NoteProps): Promise {
+ let message = '';
+
+ if (props.children) {
+ const messages: string[] = [];
+ const children = await resolveChildren(props.children);
+ for (const child of children) {
+ // TODO (43081j): handle cancelling of children
+ if (isCancel(child)) {
+ continue;
+ }
+ messages.push(String(child));
+ }
+ message = messages.join('\n');
+ } else if (props.message) {
+ message = props.message;
+ }
+
+ note(message, props.title, props);
+}
diff --git a/packages/jsx/src/components/option.ts b/packages/jsx/src/components/option.ts
new file mode 100644
index 00000000..70a56aab
--- /dev/null
+++ b/packages/jsx/src/components/option.ts
@@ -0,0 +1,31 @@
+import { type Option, isCancel } from '@clack/prompts';
+import type { JSX } from '../types.js';
+import { resolveChildren } from '../utils.js';
+
+export interface OptionProps {
+ value: unknown;
+ children?: JSX.Element | JSX.Element[] | string;
+}
+
+export async function Option(props: OptionProps): Promise