mirror of
https://github.com/chenasraf/simple-scaffold.git
synced 2026-05-17 17:28:09 +00:00
feat: select, confirm and number input types
This commit is contained in:
@@ -58,16 +58,25 @@ module.exports = {
|
||||
output: "src/components",
|
||||
inputs: {
|
||||
author: { message: "Author name", required: true },
|
||||
license: { message: "License type", default: "MIT" },
|
||||
description: { message: "Component description" },
|
||||
license: {
|
||||
type: "select",
|
||||
message: "License",
|
||||
options: ["MIT", "Apache-2.0", "GPL-3.0"],
|
||||
},
|
||||
private: { type: "confirm", message: "Private?", default: false },
|
||||
port: { type: "number", message: "Port", default: 3000 },
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
In your templates, use these as `{{ author }}`, `{{ license }}`, `{{ description }}`.
|
||||
In your templates, use these as `{{ author }}`, `{{ license }}`, `{{ private }}`, `{{ port }}`.
|
||||
|
||||
Supported input types: `text` (default), `select`, `confirm`, `number`. See
|
||||
[Template Inputs](cli#template-inputs) for the full reference.
|
||||
|
||||
- **Required** inputs are prompted interactively if not provided via `--data` or `-D`
|
||||
- **Select and confirm** inputs are always prompted unless pre-provided
|
||||
- **Optional** inputs with a `default` use that value silently if not provided
|
||||
- In non-interactive environments, only defaults are applied
|
||||
|
||||
|
||||
@@ -59,7 +59,13 @@ module.exports = {
|
||||
output: "src/components",
|
||||
inputs: {
|
||||
author: { message: "Author name", required: true },
|
||||
license: { message: "License type", default: "MIT" },
|
||||
license: {
|
||||
type: "select",
|
||||
message: "License",
|
||||
options: ["MIT", "Apache-2.0", "GPL-3.0"],
|
||||
},
|
||||
private: { type: "confirm", message: "Private package?", default: false },
|
||||
port: { type: "number", message: "Dev server port", default: 3000 },
|
||||
description: { message: "Description" },
|
||||
},
|
||||
},
|
||||
@@ -69,8 +75,18 @@ module.exports = {
|
||||
Each input becomes available as a Handlebars variable in your templates (e.g., `{{ author }}`,
|
||||
`{{ license }}`).
|
||||
|
||||
**Input types:**
|
||||
|
||||
| Type | Description | Value type |
|
||||
| --------- | ------------------------------ | ---------- |
|
||||
| `text` | Free-form text input (default) | `string` |
|
||||
| `select` | Choose from a list of options | `string` |
|
||||
| `confirm` | Yes/no prompt | `boolean` |
|
||||
| `number` | Numeric input | `number` |
|
||||
|
||||
- **Required inputs** without a value will be prompted interactively
|
||||
- **Optional inputs** with a `default` will use that value if not provided
|
||||
- **Select and confirm** inputs are always prompted (unless pre-provided)
|
||||
- **Optional text/number inputs** with a `default` will use that value silently
|
||||
- All inputs can be pre-provided via `--data` or `-D` to skip the prompt:
|
||||
|
||||
```shell
|
||||
|
||||
@@ -33,9 +33,11 @@ interface ScaffoldConfig {
|
||||
}
|
||||
|
||||
interface ScaffoldInput {
|
||||
type?: "text" | "select" | "confirm" | "number"
|
||||
message?: string
|
||||
required?: boolean
|
||||
default?: string
|
||||
default?: string | boolean | number
|
||||
options?: (string | { name: string; value: string })[] // for type: "select"
|
||||
}
|
||||
|
||||
interface AfterScaffoldContext {
|
||||
@@ -104,7 +106,13 @@ await Scaffold({
|
||||
output: "src/components",
|
||||
inputs: {
|
||||
author: { message: "Author name", required: true },
|
||||
license: { message: "License", default: "MIT" },
|
||||
license: {
|
||||
type: "select",
|
||||
message: "License",
|
||||
options: ["MIT", "Apache-2.0", "GPL-3.0"],
|
||||
},
|
||||
private: { type: "confirm", message: "Private package?", default: false },
|
||||
port: { type: "number", message: "Dev server port", default: 3000 },
|
||||
},
|
||||
})
|
||||
// In templates: {{ author }}, {{ license }}
|
||||
|
||||
@@ -53,7 +53,9 @@
|
||||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"@inquirer/confirm": "^6.0.10",
|
||||
"@inquirer/input": "^5.0.10",
|
||||
"@inquirer/number": "^4.0.10",
|
||||
"@inquirer/select": "^5.1.2",
|
||||
"date-fns": "^4.1.0",
|
||||
"glob": "^13.0.6",
|
||||
|
||||
44
pnpm-lock.yaml
generated
44
pnpm-lock.yaml
generated
@@ -7,9 +7,15 @@ settings:
|
||||
importers:
|
||||
.:
|
||||
dependencies:
|
||||
"@inquirer/confirm":
|
||||
specifier: ^6.0.10
|
||||
version: 6.0.10(@types/node@25.5.0)
|
||||
"@inquirer/input":
|
||||
specifier: ^5.0.10
|
||||
version: 5.0.10(@types/node@25.5.0)
|
||||
"@inquirer/number":
|
||||
specifier: ^4.0.10
|
||||
version: 4.0.10(@types/node@25.5.0)
|
||||
"@inquirer/select":
|
||||
specifier: ^5.1.2
|
||||
version: 5.1.2(@types/node@25.5.0)
|
||||
@@ -219,6 +225,18 @@ packages:
|
||||
}
|
||||
engines: { node: ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" }
|
||||
|
||||
"@inquirer/confirm@6.0.10":
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-tiNyA73pgpQ0FQ7axqtoLUe4GDYjNCDcVsbgcA5anvwg2z6i+suEngLKKJrWKJolT//GFPZHwN30binDIHgSgQ==,
|
||||
}
|
||||
engines: { node: ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" }
|
||||
peerDependencies:
|
||||
"@types/node": ">=18"
|
||||
peerDependenciesMeta:
|
||||
"@types/node":
|
||||
optional: true
|
||||
|
||||
"@inquirer/core@11.1.7":
|
||||
resolution:
|
||||
{
|
||||
@@ -250,6 +268,18 @@ packages:
|
||||
"@types/node":
|
||||
optional: true
|
||||
|
||||
"@inquirer/number@4.0.10":
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-Ht8OQstxiS3APMGjHV0aYAjRAysidWdwurWEo2i8yI5xbhOBWqizT0+MU1S2GCcuhIBg+3SgWVjEoXgfhY+XaA==,
|
||||
}
|
||||
engines: { node: ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" }
|
||||
peerDependencies:
|
||||
"@types/node": ">=18"
|
||||
peerDependenciesMeta:
|
||||
"@types/node":
|
||||
optional: true
|
||||
|
||||
"@inquirer/select@5.1.2":
|
||||
resolution:
|
||||
{
|
||||
@@ -1956,6 +1986,13 @@ snapshots:
|
||||
|
||||
"@inquirer/ansi@2.0.4": {}
|
||||
|
||||
"@inquirer/confirm@6.0.10(@types/node@25.5.0)":
|
||||
dependencies:
|
||||
"@inquirer/core": 11.1.7(@types/node@25.5.0)
|
||||
"@inquirer/type": 4.0.4(@types/node@25.5.0)
|
||||
optionalDependencies:
|
||||
"@types/node": 25.5.0
|
||||
|
||||
"@inquirer/core@11.1.7(@types/node@25.5.0)":
|
||||
dependencies:
|
||||
"@inquirer/ansi": 2.0.4
|
||||
@@ -1977,6 +2014,13 @@ snapshots:
|
||||
optionalDependencies:
|
||||
"@types/node": 25.5.0
|
||||
|
||||
"@inquirer/number@4.0.10(@types/node@25.5.0)":
|
||||
dependencies:
|
||||
"@inquirer/core": 11.1.7(@types/node@25.5.0)
|
||||
"@inquirer/type": 4.0.4(@types/node@25.5.0)
|
||||
optionalDependencies:
|
||||
"@types/node": 25.5.0
|
||||
|
||||
"@inquirer/select@5.1.2(@types/node@25.5.0)":
|
||||
dependencies:
|
||||
"@inquirer/ansi": 2.0.4
|
||||
|
||||
@@ -1,7 +1,15 @@
|
||||
import input from "@inquirer/input"
|
||||
import select from "@inquirer/select"
|
||||
import confirm from "@inquirer/confirm"
|
||||
import number from "@inquirer/number"
|
||||
import { colorize } from "./colors"
|
||||
import { ScaffoldCmdConfig, ScaffoldConfig, ScaffoldConfigMap, ScaffoldInput } from "./types"
|
||||
import {
|
||||
ScaffoldCmdConfig,
|
||||
ScaffoldConfig,
|
||||
ScaffoldConfigMap,
|
||||
ScaffoldInput,
|
||||
ScaffoldInputType,
|
||||
} from "./types"
|
||||
|
||||
/** Prompts the user for a scaffold name. */
|
||||
export async function promptForName(): Promise<string> {
|
||||
@@ -62,6 +70,59 @@ export async function promptForTemplates(): Promise<string[]> {
|
||||
.filter(Boolean)
|
||||
}
|
||||
|
||||
/** Prompts for a single input based on its type. */
|
||||
async function promptSingleInput(
|
||||
key: string,
|
||||
def: ScaffoldInput,
|
||||
): Promise<string | boolean | number | undefined> {
|
||||
const type: ScaffoldInputType = def.type ?? "text"
|
||||
const message = colorize.cyan(def.message ?? `${key}:`)
|
||||
|
||||
switch (type) {
|
||||
case "text":
|
||||
return input({
|
||||
message,
|
||||
required: def.required,
|
||||
default: def.default as string | undefined,
|
||||
validate: def.required
|
||||
? (value) => {
|
||||
if (!value.trim()) return `${key} is required`
|
||||
return true
|
||||
}
|
||||
: undefined,
|
||||
})
|
||||
|
||||
case "select": {
|
||||
const choices = (def.options ?? []).map((opt) =>
|
||||
typeof opt === "string" ? { name: opt, value: opt } : opt,
|
||||
)
|
||||
if (choices.length === 0) {
|
||||
throw new Error(`Input "${key}" has type "select" but no options defined`)
|
||||
}
|
||||
return select({
|
||||
message,
|
||||
choices,
|
||||
default: def.default as string | undefined,
|
||||
})
|
||||
}
|
||||
|
||||
case "confirm":
|
||||
return confirm({
|
||||
message,
|
||||
default: (def.default as boolean | undefined) ?? false,
|
||||
})
|
||||
|
||||
case "number":
|
||||
return (
|
||||
(await number({
|
||||
message,
|
||||
required: def.required,
|
||||
default: def.default as number | undefined,
|
||||
})) ?? def.default
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prompts the user for any required scaffold inputs that are not already provided in data.
|
||||
* Also applies default values for optional inputs that have one.
|
||||
@@ -79,16 +140,8 @@ export async function promptForInputs(
|
||||
continue
|
||||
}
|
||||
|
||||
if (def.required) {
|
||||
data[key] = await input({
|
||||
message: colorize.cyan(def.message ?? `${key}:`),
|
||||
required: true,
|
||||
default: def.default,
|
||||
validate: (value) => {
|
||||
if (!value.trim()) return `${key} is required`
|
||||
return true
|
||||
},
|
||||
})
|
||||
if (def.required || def.type === "select" || def.type === "confirm") {
|
||||
data[key] = await promptSingleInput(key, def)
|
||||
} else if (def.default !== undefined && !(key in data)) {
|
||||
data[key] = def.default
|
||||
}
|
||||
|
||||
30
src/types.ts
30
src/types.ts
@@ -236,18 +236,44 @@ export interface AfterScaffoldContext {
|
||||
*/
|
||||
export type AfterScaffoldHook = ((context: AfterScaffoldContext) => void | Promise<void>) | string
|
||||
|
||||
/**
|
||||
* The type of an interactive input prompt.
|
||||
*
|
||||
* - `"text"` — free-form text input (default)
|
||||
* - `"select"` — choose from a list of options
|
||||
* - `"confirm"` — yes/no boolean prompt
|
||||
* - `"number"` — numeric input
|
||||
*
|
||||
* @category Config
|
||||
*/
|
||||
export type ScaffoldInputType = "text" | "select" | "confirm" | "number"
|
||||
|
||||
/**
|
||||
* Defines a single interactive input for a scaffold template.
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* inputs: {
|
||||
* author: { message: "Author name", required: true },
|
||||
* license: { type: "select", message: "License", options: ["MIT", "Apache-2.0", "GPL-3.0"] },
|
||||
* private: { type: "confirm", message: "Private package?", default: false },
|
||||
* port: { type: "number", message: "Dev server port", default: 3000 },
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @category Config
|
||||
*/
|
||||
export interface ScaffoldInput {
|
||||
/** The type of prompt. Defaults to `"text"`. */
|
||||
type?: ScaffoldInputType
|
||||
/** The prompt message shown to the user. Defaults to the input key name if omitted. */
|
||||
message?: string
|
||||
/** Whether this input must be provided. If true and missing, the user will be prompted interactively. */
|
||||
required?: boolean
|
||||
/** Default value used when the user doesn't provide one. */
|
||||
default?: string
|
||||
/** Default value. Type depends on the input type: string for text/select, boolean for confirm, number for number. */
|
||||
default?: string | boolean | number
|
||||
/** List of options for `type: "select"`. Each can be a string or `{ name, value }`. */
|
||||
options?: (string | { name: string; value: string })[]
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -9,8 +9,18 @@ vi.mock("@inquirer/select", () => ({
|
||||
default: vi.fn(),
|
||||
}))
|
||||
|
||||
vi.mock("@inquirer/confirm", () => ({
|
||||
default: vi.fn(),
|
||||
}))
|
||||
|
||||
vi.mock("@inquirer/number", () => ({
|
||||
default: vi.fn(),
|
||||
}))
|
||||
|
||||
import inputMock from "@inquirer/input"
|
||||
import selectMock from "@inquirer/select"
|
||||
import confirmMock from "@inquirer/confirm"
|
||||
import numberMock from "@inquirer/number"
|
||||
import {
|
||||
promptForName,
|
||||
promptForTemplateKey,
|
||||
@@ -83,7 +93,9 @@ describe("prompts", () => {
|
||||
b: { name: "b", templates: [], output: "" },
|
||||
c: { name: "c", templates: [], output: "" },
|
||||
})
|
||||
const call = vi.mocked(selectMock).mock.calls[0][0] as { choices: { name: string; value: string }[] }
|
||||
const call = vi.mocked(selectMock).mock.calls[0][0] as {
|
||||
choices: { name: string; value: string }[]
|
||||
}
|
||||
expect(call.choices).toEqual([
|
||||
{ name: "a", value: "a" },
|
||||
{ name: "b", value: "b" },
|
||||
@@ -131,9 +143,9 @@ describe("prompts", () => {
|
||||
test("prompts for all missing values when interactive", async () => {
|
||||
mockTTY(true)
|
||||
vi.mocked(inputMock)
|
||||
.mockResolvedValueOnce("my-app") // name
|
||||
.mockResolvedValueOnce("./output") // output
|
||||
.mockResolvedValueOnce("src/tpl") // templates
|
||||
.mockResolvedValueOnce("my-app") // name
|
||||
.mockResolvedValueOnce("./output") // output
|
||||
.mockResolvedValueOnce("src/tpl") // templates
|
||||
|
||||
const config = { ...blankConfig }
|
||||
const result = await promptForMissingConfig(config)
|
||||
@@ -162,9 +174,9 @@ describe("prompts", () => {
|
||||
test("prompts for template key when multiple templates and no key", async () => {
|
||||
mockTTY(true)
|
||||
vi.mocked(inputMock)
|
||||
.mockResolvedValueOnce("name") // name
|
||||
.mockResolvedValueOnce("./output") // output
|
||||
.mockResolvedValueOnce("src/tpl") // templates
|
||||
.mockResolvedValueOnce("name") // name
|
||||
.mockResolvedValueOnce("./output") // output
|
||||
.mockResolvedValueOnce("src/tpl") // templates
|
||||
vi.mocked(selectMock).mockResolvedValue("component")
|
||||
|
||||
const configMap = {
|
||||
@@ -183,7 +195,13 @@ describe("prompts", () => {
|
||||
default: { name: "d", templates: [], output: "" },
|
||||
component: { name: "c", templates: [], output: "" },
|
||||
}
|
||||
const config = { ...blankConfig, name: "test", output: "./out", templates: ["tpl"], key: "default" }
|
||||
const config = {
|
||||
...blankConfig,
|
||||
name: "test",
|
||||
output: "./out",
|
||||
templates: ["tpl"],
|
||||
key: "default",
|
||||
}
|
||||
const result = await promptForMissingConfig(config, configMap)
|
||||
expect(result.key).toEqual("default")
|
||||
expect(selectMock).not.toHaveBeenCalled()
|
||||
@@ -227,7 +245,7 @@ describe("prompts", () => {
|
||||
|
||||
test("only prompts for missing values, not provided ones", async () => {
|
||||
mockTTY(true)
|
||||
vi.mocked(inputMock).mockResolvedValueOnce("src/tpl") // only templates missing
|
||||
vi.mocked(inputMock).mockResolvedValueOnce("src/tpl") // only templates missing
|
||||
|
||||
const config = { ...blankConfig, name: "app", output: "./out" }
|
||||
const result = await promptForMissingConfig(config)
|
||||
@@ -259,10 +277,7 @@ describe("prompts", () => {
|
||||
})
|
||||
|
||||
test("applies default value for optional inputs not in data", async () => {
|
||||
const result = await promptForInputs(
|
||||
{ license: { default: "MIT" } },
|
||||
{},
|
||||
)
|
||||
const result = await promptForInputs({ license: { default: "MIT" } }, {})
|
||||
expect(result.license).toEqual("MIT")
|
||||
expect(inputMock).not.toHaveBeenCalled()
|
||||
})
|
||||
@@ -277,18 +292,13 @@ describe("prompts", () => {
|
||||
|
||||
test("uses input key as message fallback", async () => {
|
||||
vi.mocked(inputMock).mockResolvedValueOnce("val")
|
||||
await promptForInputs(
|
||||
{ myField: { required: true } },
|
||||
{},
|
||||
)
|
||||
await promptForInputs({ myField: { required: true } }, {})
|
||||
const call = vi.mocked(inputMock).mock.calls[0][0] as { message: string }
|
||||
expect(call.message).toContain("myField")
|
||||
})
|
||||
|
||||
test("prompts multiple required inputs in order", async () => {
|
||||
vi.mocked(inputMock)
|
||||
.mockResolvedValueOnce("John")
|
||||
.mockResolvedValueOnce("2.0")
|
||||
vi.mocked(inputMock).mockResolvedValueOnce("John").mockResolvedValueOnce("2.0")
|
||||
const result = await promptForInputs(
|
||||
{
|
||||
author: { message: "Author", required: true },
|
||||
@@ -318,23 +328,143 @@ describe("prompts", () => {
|
||||
})
|
||||
|
||||
test("preserves existing data keys not in inputs", async () => {
|
||||
const result = await promptForInputs(
|
||||
{ license: { default: "MIT" } },
|
||||
{ extra: "value" },
|
||||
)
|
||||
const result = await promptForInputs({ license: { default: "MIT" } }, { extra: "value" })
|
||||
expect(result.extra).toEqual("value")
|
||||
expect(result.license).toEqual("MIT")
|
||||
})
|
||||
|
||||
test("required input with default pre-fills prompt", async () => {
|
||||
vi.mocked(inputMock).mockResolvedValueOnce("custom")
|
||||
await promptForInputs(
|
||||
{ author: { required: true, default: "Anonymous" } },
|
||||
{},
|
||||
)
|
||||
await promptForInputs({ author: { required: true, default: "Anonymous" } }, {})
|
||||
const call = vi.mocked(inputMock).mock.calls[0][0] as { default?: string }
|
||||
expect(call.default).toEqual("Anonymous")
|
||||
})
|
||||
|
||||
test("select input prompts with options", async () => {
|
||||
vi.mocked(selectMock).mockResolvedValueOnce("MIT")
|
||||
const result = await promptForInputs(
|
||||
{
|
||||
license: {
|
||||
type: "select",
|
||||
message: "License",
|
||||
options: ["MIT", "Apache-2.0", "GPL-3.0"],
|
||||
},
|
||||
},
|
||||
{},
|
||||
)
|
||||
expect(result.license).toEqual("MIT")
|
||||
expect(selectMock).toHaveBeenCalledOnce()
|
||||
const call = vi.mocked(selectMock).mock.calls[0][0] as {
|
||||
choices: { name: string; value: string }[]
|
||||
}
|
||||
expect(call.choices).toEqual([
|
||||
{ name: "MIT", value: "MIT" },
|
||||
{ name: "Apache-2.0", value: "Apache-2.0" },
|
||||
{ name: "GPL-3.0", value: "GPL-3.0" },
|
||||
])
|
||||
})
|
||||
|
||||
test("select input with object options", async () => {
|
||||
vi.mocked(selectMock).mockResolvedValueOnce("mit")
|
||||
const result = await promptForInputs(
|
||||
{
|
||||
license: {
|
||||
type: "select",
|
||||
options: [
|
||||
{ name: "MIT License", value: "mit" },
|
||||
{ name: "Apache 2.0", value: "apache" },
|
||||
],
|
||||
},
|
||||
},
|
||||
{},
|
||||
)
|
||||
expect(result.license).toEqual("mit")
|
||||
})
|
||||
|
||||
test("select input throws when no options", async () => {
|
||||
await expect(promptForInputs({ license: { type: "select" } }, {})).rejects.toThrow(
|
||||
"no options defined",
|
||||
)
|
||||
})
|
||||
|
||||
test("select input skipped when value already provided", async () => {
|
||||
const result = await promptForInputs(
|
||||
{ license: { type: "select", options: ["MIT", "Apache"] } },
|
||||
{ license: "MIT" },
|
||||
)
|
||||
expect(result.license).toEqual("MIT")
|
||||
expect(selectMock).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
test("confirm input prompts and returns boolean", async () => {
|
||||
vi.mocked(confirmMock).mockResolvedValueOnce(true)
|
||||
const result = await promptForInputs(
|
||||
{ private: { type: "confirm", message: "Private?" } },
|
||||
{},
|
||||
)
|
||||
expect(result.private).toBe(true)
|
||||
expect(confirmMock).toHaveBeenCalledOnce()
|
||||
})
|
||||
|
||||
test("confirm input with default false", async () => {
|
||||
vi.mocked(confirmMock).mockResolvedValueOnce(false)
|
||||
await promptForInputs({ private: { type: "confirm", default: false } }, {})
|
||||
const call = vi.mocked(confirmMock).mock.calls[0][0] as { default?: boolean }
|
||||
expect(call.default).toBe(false)
|
||||
})
|
||||
|
||||
test("confirm input skipped when value already provided", async () => {
|
||||
const result = await promptForInputs({ private: { type: "confirm" } }, { private: true })
|
||||
expect(result.private).toBe(true)
|
||||
expect(confirmMock).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
test("number input prompts and returns number", async () => {
|
||||
vi.mocked(numberMock).mockResolvedValueOnce(8080)
|
||||
const result = await promptForInputs(
|
||||
{ port: { type: "number", message: "Port", required: true } },
|
||||
{},
|
||||
)
|
||||
expect(result.port).toBe(8080)
|
||||
expect(numberMock).toHaveBeenCalledOnce()
|
||||
})
|
||||
|
||||
test("number input with default", async () => {
|
||||
vi.mocked(numberMock).mockResolvedValueOnce(3000)
|
||||
await promptForInputs({ port: { type: "number", default: 3000, required: true } }, {})
|
||||
const call = vi.mocked(numberMock).mock.calls[0][0] as { default?: number }
|
||||
expect(call.default).toBe(3000)
|
||||
})
|
||||
|
||||
test("number input skipped when value already provided", async () => {
|
||||
const result = await promptForInputs(
|
||||
{ port: { type: "number", required: true } },
|
||||
{ port: 9090 },
|
||||
)
|
||||
expect(result.port).toBe(9090)
|
||||
expect(numberMock).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
test("mixed input types in one config", async () => {
|
||||
vi.mocked(inputMock).mockResolvedValueOnce("John")
|
||||
vi.mocked(selectMock).mockResolvedValueOnce("MIT")
|
||||
vi.mocked(confirmMock).mockResolvedValueOnce(true)
|
||||
vi.mocked(numberMock).mockResolvedValueOnce(3000)
|
||||
|
||||
const result = await promptForInputs(
|
||||
{
|
||||
author: { type: "text", message: "Author", required: true },
|
||||
license: { type: "select", message: "License", options: ["MIT", "Apache"] },
|
||||
private: { type: "confirm", message: "Private?" },
|
||||
port: { type: "number", message: "Port", required: true },
|
||||
},
|
||||
{},
|
||||
)
|
||||
expect(result.author).toEqual("John")
|
||||
expect(result.license).toEqual("MIT")
|
||||
expect(result.private).toBe(true)
|
||||
expect(result.port).toBe(3000)
|
||||
})
|
||||
})
|
||||
|
||||
describe("resolveInputs", () => {
|
||||
|
||||
Reference in New Issue
Block a user