Skip to content

Commit 4bd85a8

Browse files
authored
feat: migrate prompts to Clack (#305)
* refactor(prompts): add text/password/editor wrappers in lib/prompts.ts * refactor(init): route input through lib/prompts.ts * refactor(api): route input/editor through lib/prompts.ts * refactor(deploy): route input/password through lib/prompts.ts * refactor(users): route input/password through lib/prompts.ts * refactor(app-picker): route input through lib/prompts.ts * test: stub new lib/prompts.ts exports in existing mocks * build(deps): add @clack/prompts and external-editor * feat(prompts): swap confirm to @clack/prompts * feat(prompts): swap text to @clack/prompts * feat(prompts): swap password to @clack/prompts * feat(prompts): implement editor via external-editor * refactor(prompts): adopt clack validator contract at call sites * test: route prompt mocks through lib/prompts.ts * feat(listage): swap select/search internals to @clack/prompts * test(listage): rewrite tests against @clack/prompts internals * test(listage): drop ttyContext from stubs and integration harness * feat(spinner): swap intro/outro/spinner to @clack/prompts * refactor(cli): drop ExitPromptError handling; rely on UserAbortError * refactor(prompts): route doctor and update confirms through lib/prompts.ts * build(deps): remove @inquirer/* packages * docs(changeset): refresh prompt UI with @clack/prompts * feat(intro): wrap apps commands * feat(intro): wrap users commands * feat(intro): wrap config commands * feat(intro): wrap orgs and billing toggles * feat(intro): wrap auth, link, whoami, switch-env, unlink * feat(intro): wrap env pull, api, skill install * refactor(intro): retitle init, doctor, update, and open to operation style * feat(intro): route human output through prompt rail * feat(intro): route apps list through clack ui * fix(review): address prompt lifecycle feedback - pair users list intro/outro around human output only - avoid app create UI wrapping in JSON and agent output - ensure config push closes intro/outro across early exits * fix(prompts): preserve clack prompt edge cases * fix(prompts): correct interactive command outro statuses * fix(review): address PR #305 prompt gutter feedback - add shared gutter wrapper for guaranteed intro/outro cleanup - route unguarded prompt rail commands through the wrapper - merge duplicate apps list error imports
1 parent f95eca2 commit 4bd85a8

66 files changed

Lines changed: 2162 additions & 1374 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"clerk": minor
3+
---
4+
5+
Refresh the visual style of prompts, lists, spinners, and intro/outro brackets to use `@clack/prompts`, and make interactive command endings reflect success, failure, or paused cancellation status.

bun.lock

Lines changed: 21 additions & 41 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,12 @@
3434
"@changesets/cli": "^2.31.0",
3535
"@clerk/testing": "^2.0.33",
3636
"@types/bun": "^1.3.14",
37+
"@types/semver": "^7.7.1",
3738
"nano-staged": "^1.0.2",
3839
"oxfmt": "^0.47.0",
3940
"oxlint": "^1.62.0",
4041
"playwright": "^1.60.0",
42+
"semver": "^7.8.0",
4143
"typescript": "^6"
4244
},
4345
"nano-staged": {

packages/cli-core/package.json

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,13 @@
1717
"test": "bun test src/ --parallel"
1818
},
1919
"dependencies": {
20+
"@clack/prompts": "^1.3.0",
2021
"@clerk/cli-extras": "workspace:*",
2122
"@commander-js/extra-typings": "^14.0.0",
22-
"@inquirer/ansi": "^2.0.5",
23-
"@inquirer/core": "^11.1.9",
24-
"@inquirer/figures": "^2.0.5",
25-
"@inquirer/prompts": "^8.4.3",
2623
"@napi-rs/keyring": "^1.3.0",
2724
"commander": "^14.0.3",
2825
"env-paths": "^4.0.0",
26+
"external-editor": "^3.1.0",
2927
"magicast": "^0.5.3",
3028
"semver": "^7.8.1",
3129
"yaml": "^2.9.0"

packages/cli-core/src/commands/api/index.test.ts

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,13 @@ import { test, expect, describe, beforeEach, afterEach, spyOn, mock } from "bun:
22
import { mkdtemp, rm } from "node:fs/promises";
33
import { join } from "node:path";
44
import { tmpdir } from "node:os";
5-
import { CliError, ERROR_CODE } from "../../lib/errors.ts";
5+
import { CliError, ERROR_CODE, UserAbortError } from "../../lib/errors.ts";
66
import {
77
useCaptureLog,
88
credentialStoreStubs,
99
gitStubs,
1010
configStubs,
11-
promptsStubs,
11+
libPromptsStubs,
1212
stubFetch,
1313
} from "../../test/lib/stubs.ts";
1414

@@ -171,7 +171,11 @@ mock.module("../../lib/config.ts", () => ({
171171
},
172172
}));
173173

174-
mock.module("@inquirer/prompts", () => promptsStubs);
174+
const mockConfirm = mock(async (_config?: unknown) => true);
175+
mock.module("../../lib/prompts.ts", () => ({
176+
...libPromptsStubs,
177+
confirm: (config: unknown) => mockConfirm(config),
178+
}));
175179

176180
const { _setConfigDir } = (await import("../../lib/config.ts")) as any;
177181
const { setMode } = (await import("../../mode.ts")) as any;
@@ -209,6 +213,8 @@ describe("api command", () => {
209213
throw new Error("process.exit");
210214
});
211215
stubFetch(async () => new Response(JSON.stringify(mockUsers), { status: 200 }));
216+
mockConfirm.mockReset();
217+
mockConfirm.mockResolvedValue(true);
212218
});
213219

214220
afterEach(async () => {
@@ -479,12 +485,29 @@ describe("api command", () => {
479485
});
480486

481487
test("prints API error response body to stdout and exits 1", async () => {
488+
setMode("human");
482489
const errorBody = { errors: [{ message: "not found", code: "resource_not_found" }] };
483490
stubFetch(async () => new Response(JSON.stringify(errorBody), { status: 404 }));
484491

485492
await runApi("/users/bad_id");
486493
expect(process.exitCode).toBe(1);
487494
expect(captured.out).toContain(JSON.stringify(errorBody, null, 2));
495+
expect(captured.err).toContain("Failed");
496+
expect(captured.err).not.toContain("Done");
497+
});
498+
499+
test("shows Paused with instructions when a confirmation prompt is cancelled", async () => {
500+
setMode("human");
501+
mockConfirm.mockImplementation(async () => {
502+
throw new UserAbortError();
503+
});
504+
505+
await expect(
506+
runApi("/users", { method: "POST", data: "{}", yes: false }),
507+
).rejects.toBeInstanceOf(UserAbortError);
508+
expect(captured.err).toContain("Paused");
509+
expect(captured.err).toContain("Run this command again to continue.");
510+
expect(captured.err).not.toContain("Done");
488511
});
489512

490513
test("--include shows headers on error responses too", async () => {

0 commit comments

Comments
 (0)