diff --git a/.prettierignore b/.prettierignore
new file mode 100644
index 0000000..feacb45
--- /dev/null
+++ b/.prettierignore
@@ -0,0 +1,4 @@
+docs/docs/api/
+examples/
+.github/
+CHANGELOG.md
diff --git a/README.md b/README.md
index fa1d1b9..635629d 100644
--- a/README.md
+++ b/README.md
@@ -13,9 +13,12 @@
-Simple Scaffold is a file scaffolding tool. You define templates once, then generate files from them whenever you need — whether it's a single component or an entire app boilerplate.
+Simple Scaffold is a file scaffolding tool. You define templates once, then generate files from them
+whenever you need — whether it's a single component or an entire app boilerplate.
-Templates use **Handlebars.js** syntax, so you can inject data, loop over lists, use conditionals, and write custom helpers. It works as a CLI or as a Node.js library, and it doesn't care what kind of files you're generating.
+Templates use **Handlebars.js** syntax, so you can inject data, loop over lists, use conditionals,
+and write custom helpers. It works as a CLI or as a Node.js library, and it doesn't care what kind
+of files you're generating.
@@ -92,6 +95,33 @@ See information about each option and flag using the `--help` flag, or read the
[CLI documentation](https://chenasraf.github.io/simple-scaffold/docs/usage/cli). For information
about how configuration files work, [see below](#configuration-files).
+### Interactive Mode
+
+When running in a terminal, Simple Scaffold will interactively prompt for any missing required
+values — name, output directory, template paths, and template key (if multiple are available).
+
+Config files can also define **inputs** — custom fields that are prompted interactively and become
+template data:
+
+```js
+module.exports = {
+ component: {
+ templates: ["templates/component"],
+ output: "src/components",
+ inputs: {
+ author: { message: "Author name", required: true },
+ license: { message: "License", default: "MIT" },
+ },
+ },
+}
+```
+
+Inputs can be pre-provided via `--data` or `-D` to skip the prompt:
+
+```sh
+npx simple-scaffold -c scaffold.config.js -k component -D author=John MyComponent
+```
+
### Configuration Files
You can use a config file to more easily maintain all your scaffold definitions.
diff --git a/docs/docs/usage/02-configuration_files.md b/docs/docs/usage/02-configuration_files.md
index 72ebe4a..dae2781 100644
--- a/docs/docs/usage/02-configuration_files.md
+++ b/docs/docs/usage/02-configuration_files.md
@@ -23,7 +23,8 @@ module.exports = {
}
```
-For the full configuration options, see [ScaffoldConfigFile](../api/type-aliases/ScaffoldConfigFile).
+For the full configuration options, see
+[ScaffoldConfigFile](../api/type-aliases/ScaffoldConfigFile).
If you want to supply functions inside the configurations, you must use a `.js`/`.cjs`/`.mjs` file
as JSON does not support non-primitives.
@@ -45,6 +46,31 @@ module.exports = (config) => {
}
```
+### Template Inputs
+
+You can define **inputs** in your config to prompt users for custom values when scaffolding. Each
+input becomes a template data variable:
+
+```js
+module.exports = {
+ component: {
+ templates: ["templates/component"],
+ output: "src/components",
+ inputs: {
+ author: { message: "Author name", required: true },
+ license: { message: "License type", default: "MIT" },
+ description: { message: "Component description" },
+ },
+ },
+}
+```
+
+In your templates, use these as `{{ author }}`, `{{ license }}`, `{{ description }}`.
+
+- **Required** inputs are prompted interactively if not provided via `--data` or `-D`
+- **Optional** inputs with a `default` use that value silently if not provided
+- In non-interactive environments, only defaults are applied
+
If you want to provide templates that need no name (such as common config files which are easily
portable between projects), you may provide the `name` property in the config object.
@@ -86,7 +112,6 @@ simple-scaffold -c scaffold.json MyComponentName
```
- When the a directory is given, the following files in the given directory will be tried in order:
-
- `scaffold.config.*`
- `scaffold.*`
diff --git a/docs/docs/usage/03-cli.md b/docs/docs/usage/03-cli.md
index 2fea068..1995306 100644
--- a/docs/docs/usage/03-cli.md
+++ b/docs/docs/usage/03-cli.md
@@ -13,24 +13,68 @@ To see this and more information anytime, add the `-h` or `--help` flag to your
Options:
-| Option/flag \| Alias | Description |
-| ------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| `--name` \| `-n` | Name to be passed to the generated files. `{{name}}` and other data parameters inside contents and file names will be replaced accordingly. You may omit the `--name` or `-n` for this specific option. |
-| `--config` \| `-c` | Filename or directory to load config from |
-| `--git` \| `-g` | Git URL or GitHub path to load a template from. |
-| `--key` \| `-k` | Key to load inside the config file. This overwrites the config key provided after the colon in `--config` (e.g. `--config scaffold.cmd.js:component)` |
-| `--output` \| `-o` | Path to output to. If `--subdir` is enabled, the subdir will be created inside this path. Default is current working directory. |
-| `--templates` \| `-t` | Template files to use as input. You may provide multiple files, each of which can be a relative or absolute path, or a glob pattern for multiple file matching easily. |
-| `--overwrite` \| `-w` \| `--no-overwrite` \| `-W` | Enable to override output files, even if they already exist. (default: false) |
-| `--data` \| `-d` | Add custom data to the templates. By default, only your app name is included. |
-| `--append-data` \| `-D` | Append additional custom data to the templates, which will overwrite `--data`, using an alternate syntax, which is easier to use with CLI: `-D key1=string -D key2:=raw` |
-| `--subdir` \| `-s` \| `--no-subdir` \| `-S` | Create a parent directory with the input name (and possibly `--subdir-helper` (default: false) |
-| `--subdir-helper` \| `-H` | Default helper to apply to subdir name when using `--subdir`. |
-| `--quiet` \| `-q` | Suppress output logs (Same as `--log-level none`)(default: false) |
-| `--log-level` \| `-l` | Determine amount of logs to display. The values are: `none, debug, info, warn, error`. The provided level will display messages of the same level or higher. (default: info) |
-| `--before-write` \| `-B` | Run a script before writing the files. This can be a command or a path to a file. A temporary file path will be passed to the given command and the command should return a string for the final output. |
-| `--dry-run` \| `-dr` | Don't emit files. This is good for testing your scaffolds and making sure they don't fail, without having to write actual file contents or create directories. (default: false) |
-| `--version` \| `-v` | Display version. |
+| Option/flag \| Alias | Description |
+| ------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `--name` \| `-n` | Name to be passed to the generated files. `{{name}}` and other data parameters inside contents and file names will be replaced accordingly. If omitted in an interactive terminal, you will be prompted. |
+| `--config` \| `-c` | Filename or directory to load config from |
+| `--git` \| `-g` | Git URL or GitHub path to load a template from. |
+| `--key` \| `-k` | Key to load inside the config file. If omitted and multiple templates are available, you will be prompted to select one. |
+| `--output` \| `-o` | Path to output to. If `--subdir` is enabled, the subdir will be created inside this path. If omitted in an interactive terminal, you will be prompted. |
+| `--templates` \| `-t` | Template files to use as input. You may provide multiple files, each of which can be a relative or absolute path, or a glob pattern for multiple file matching easily. If omitted in an interactive terminal, you will be prompted for a comma-separated list. |
+| `--overwrite` \| `-w` \| `--no-overwrite` \| `-W` | Enable to override output files, even if they already exist. (default: false) |
+| `--data` \| `-d` | Add custom data to the templates. By default, only your app name is included. |
+| `--append-data` \| `-D` | Append additional custom data to the templates, which will overwrite `--data`, using an alternate syntax, which is easier to use with CLI: `-D key1=string -D key2:=raw` |
+| `--subdir` \| `-s` \| `--no-subdir` \| `-S` | Create a parent directory with the input name (and possibly `--subdir-helper` (default: false) |
+| `--subdir-helper` \| `-H` | Default helper to apply to subdir name when using `--subdir`. |
+| `--quiet` \| `-q` | Suppress output logs (Same as `--log-level none`)(default: false) |
+| `--log-level` \| `-l` | Determine amount of logs to display. The values are: `none, debug, info, warn, error`. The provided level will display messages of the same level or higher. (default: info) |
+| `--before-write` \| `-B` | Run a script before writing the files. This can be a command or a path to a file. A temporary file path will be passed to the given command and the command should return a string for the final output. |
+| `--dry-run` \| `-dr` | Don't emit files. This is good for testing your scaffolds and making sure they don't fail, without having to write actual file contents or create directories. (default: false) |
+| `--version` \| `-v` | Display version. |
+
+### Interactive Mode
+
+When running in a terminal (TTY), Simple Scaffold will prompt for any missing required values:
+
+- **Name** — text input if `--name` is not provided
+- **Template key** — selectable list if `--key` is not provided and the config file has multiple
+ templates
+- **Output directory** — text input if `--output` is not provided
+- **Template paths** — comma-separated text input if `--templates` is not provided
+
+In non-interactive environments (CI, piped input), missing values will cause an error instead of
+prompting.
+
+### Template Inputs
+
+Config files can define **inputs** — custom fields that are prompted interactively and injected as
+template data. This is useful for templates that need user-specific values like author name,
+license, or description.
+
+```js
+module.exports = {
+ component: {
+ templates: ["templates/component"],
+ output: "src/components",
+ inputs: {
+ author: { message: "Author name", required: true },
+ license: { message: "License type", default: "MIT" },
+ description: { message: "Description" },
+ },
+ },
+}
+```
+
+Each input becomes available as a Handlebars variable in your templates (e.g., `{{ author }}`,
+`{{ license }}`).
+
+- **Required inputs** without a value will be prompted interactively
+- **Optional inputs** with a `default` will use that value if not provided
+- All inputs can be pre-provided via `--data` or `-D` to skip the prompt:
+
+```shell
+simple-scaffold -c scaffold.config.js -k component -D author=John -D license=Apache-2.0 MyComponent
+```
### Before Write option
diff --git a/docs/docs/usage/04-node.md b/docs/docs/usage/04-node.md
index 6e66c31..50a4d46 100644
--- a/docs/docs/usage/04-node.md
+++ b/docs/docs/usage/04-node.md
@@ -7,12 +7,9 @@ title: Node.js Usage
You can build the scaffold yourself, if you want to create more complex arguments, scaffold groups,
etc - simply pass a config object to the Scaffold function when you are ready to start.
-The config takes similar arguments to the command line. The full type definitions can be found in
-[src/types.ts](https://github.com/chenasraf/simple-scaffold/blob/develop/src/types.ts#L13).
-
-See the full
-[documentation](https://chenasraf.github.io/simple-scaffold/interfaces/ScaffoldConfig.html) for the
-configuration options and their behavior.
+The config takes similar arguments to the command line. See the full
+[API documentation](https://chenasraf.github.io/simple-scaffold/docs/api/interfaces/ScaffoldConfig)
+for all configuration options and their behavior.
```ts
interface ScaffoldConfig {
@@ -20,19 +17,25 @@ interface ScaffoldConfig {
templates: string[]
output: FileResponse
subdir?: boolean
- data?: Record
+ data?: Record
overwrite?: FileResponse
- quiet?: boolean
- verbose?: LogLevel
+ logLevel?: LogLevel
dryRun?: boolean
helpers?: Record
subdirHelper?: DefaultHelpers | string
+ inputs?: Record
beforeWrite?(
content: Buffer,
rawContent: Buffer,
outputPath: string,
): string | Buffer | undefined | Promise
}
+
+interface ScaffoldInput {
+ message?: string
+ required?: boolean
+ default?: string
+}
```
### Before Write option
@@ -46,6 +49,31 @@ to be used as the file contents.
Returning `undefined` will keep the file contents as-is, after normal Handlebars.js procesing by
Simple Scaffold.
+### Inputs
+
+The `inputs` option lets you define fields that will be prompted interactively (when running in a
+TTY) and merged into the template data. This is useful when your templates need user-specific
+values.
+
+```typescript
+import Scaffold from "simple-scaffold"
+
+await Scaffold({
+ name: "component",
+ templates: ["templates/component"],
+ output: "src/components",
+ inputs: {
+ author: { message: "Author name", required: true },
+ license: { message: "License", default: "MIT" },
+ },
+})
+// In templates: {{ author }}, {{ license }}
+```
+
+- **Required** inputs are prompted if not already in `data`
+- **Optional** inputs with a `default` are applied silently
+- Pre-providing values in `data` skips the prompt for that input
+
## Example
This is an example of loading a complete scaffold via Node.js:
@@ -53,7 +81,7 @@ This is an example of loading a complete scaffold via Node.js:
```typescript
import Scaffold from "simple-scaffold"
-const config = {
+await Scaffold({
name: "component",
templates: [path.join(__dirname, "scaffolds", "component")],
output: path.join(__dirname, "src", "components"),
@@ -65,10 +93,12 @@ const config = {
helpers: {
twice: (text) => [text, text].join(" "),
},
+ inputs: {
+ author: { message: "Author name", required: true },
+ license: { message: "License", default: "MIT" },
+ },
// return a string to replace the final file contents after pre-processing, or `undefined`
// to keep it as-is
beforeWrite: (content, rawContent, outputPath) => content.toString().toUpperCase(),
-}
-
-const scaffold = Scaffold(config)
+})
```
diff --git a/docs/docs/usage/05-examples.md b/docs/docs/usage/05-examples.md
index 8538caa..90c23d5 100644
--- a/docs/docs/usage/05-examples.md
+++ b/docs/docs/usage/05-examples.md
@@ -31,7 +31,6 @@ title: Examples
### Output
- Output file path:
-
- With `subdir = false` (default):
```text
diff --git a/docs/docs/usage/index.md b/docs/docs/usage/index.md
index c20d504..64927a5 100644
--- a/docs/docs/usage/index.md
+++ b/docs/docs/usage/index.md
@@ -3,9 +3,9 @@ title: Usage
sidebar_position: 0
---
-- [CLI Usage](cli)
+- [Template Files](templates)
- [Configuration Files](configuration_files)
+- [CLI Usage](cli)
+- [Node.js Usage](node)
- [Examples](examples)
- [Migration](migration)
-- [Node.js Usage](node)
-- [Template Files](templates)
diff --git a/src/cmd.ts b/src/cmd.ts
index edb3180..8aa1bc1 100644
--- a/src/cmd.ts
+++ b/src/cmd.ts
@@ -10,7 +10,7 @@ import { log } from "./logger"
import { MassargCommand } from "massarg/command"
import { getUniqueTmpPath as generateUniqueTmpPath } from "./file"
import { colorize } from "./colors"
-import { isInteractive, promptForMissingConfig } from "./prompts"
+import { promptForMissingConfig, resolveInputs } from "./prompts"
export async function parseCliArgs(args = process.argv.slice(2)) {
const isProjectRoot = Boolean(await fs.stat(path.join(__dirname, "package.json")).catch(() => false))
@@ -45,7 +45,8 @@ export async function parseCliArgs(args = process.argv.slice(2)) {
log(config, LogLevel.debug, "Parsing config file...", config)
const parsed = await parseConfigFile(config)
- await Scaffold(parsed)
+ const resolved = await resolveInputs(parsed)
+ await Scaffold(resolved)
} catch (e) {
const message = "message" in (e as object) ? (e as Error).message : e?.toString()
log(config, LogLevel.error, message)
diff --git a/src/prompts.ts b/src/prompts.ts
index 6808e1e..d04d107 100644
--- a/src/prompts.ts
+++ b/src/prompts.ts
@@ -1,7 +1,7 @@
import input from "@inquirer/input"
import select from "@inquirer/select"
import { colorize } from "./colors"
-import { ScaffoldCmdConfig, ScaffoldConfigMap } from "./types"
+import { ScaffoldCmdConfig, ScaffoldConfig, ScaffoldConfigMap, ScaffoldInput } from "./types"
/** Prompts the user for a scaffold name. */
export async function promptForName(): Promise {
@@ -59,6 +59,41 @@ export async function promptForTemplates(): Promise {
return value.split(",").map((t) => t.trim()).filter(Boolean)
}
+/**
+ * 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.
+ * Returns the merged data object.
+ */
+export async function promptForInputs(
+ inputs: Record,
+ existingData: Record = {},
+): Promise> {
+ const data = { ...existingData }
+
+ for (const [key, def] of Object.entries(inputs)) {
+ // Skip if already provided via data/CLI
+ if (key in data && data[key] !== undefined && data[key] !== "") {
+ 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
+ },
+ })
+ } else if (def.default !== undefined && !(key in data)) {
+ data[key] = def.default
+ }
+ }
+
+ return data
+}
+
/** Returns true if the process is running in an interactive terminal. */
export function isInteractive(): boolean {
return Boolean(process.stdin.isTTY)
@@ -97,3 +132,30 @@ export async function promptForMissingConfig(
return config
}
+
+/**
+ * Prompts for any required inputs defined in the scaffold config and merges them into data.
+ * Only prompts in interactive mode; in non-interactive mode, only applies defaults.
+ */
+export async function resolveInputs(config: ScaffoldConfig): Promise {
+ if (!config.inputs) {
+ return config
+ }
+
+ const interactive = isInteractive()
+
+ if (interactive) {
+ config.data = await promptForInputs(config.inputs, config.data)
+ } else {
+ // Non-interactive: only apply defaults
+ const data = { ...config.data }
+ for (const [key, def] of Object.entries(config.inputs)) {
+ if (def.default !== undefined && !(key in data)) {
+ data[key] = def.default
+ }
+ }
+ config.data = data
+ }
+
+ return config
+}
diff --git a/src/scaffold.ts b/src/scaffold.ts
index f006ab1..27d64c3 100644
--- a/src/scaffold.ts
+++ b/src/scaffold.ts
@@ -14,6 +14,7 @@ import { LogLevel, MinimalConfig, Resolver, ScaffoldCmdConfig, ScaffoldConfig }
import { registerHelpers } from "./parser"
import { log, logInitStep } from "./logger"
import { parseConfigFile } from "./config"
+import { resolveInputs } from "./prompts"
/**
* Create a scaffold using given `options`.
@@ -50,6 +51,7 @@ import { parseConfigFile } from "./config"
export async function Scaffold(config: ScaffoldConfig): Promise {
config.output ??= process.cwd()
+ config = await resolveInputs(config)
registerHelpers(config)
try {
config.data = { name: config.name, ...config.data }
diff --git a/src/types.ts b/src/types.ts
index 9dd5b9d..b64f9b4 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -166,10 +166,47 @@ export interface ScaffoldConfig {
outputPath: string,
): string | Buffer | undefined | Promise
+ /**
+ * Defines interactive inputs for the template. Each input becomes a template data variable.
+ *
+ * When running interactively, required inputs that are not already provided via `data` or CLI args
+ * will be prompted for. Optional inputs without a value will use their `default` if defined.
+ *
+ * @example
+ * ```typescript
+ * Scaffold({
+ * // ...
+ * inputs: {
+ * author: { message: "Author name", required: true },
+ * license: { message: "License", default: "MIT" },
+ * },
+ * })
+ * ```
+ *
+ * In templates: `{{ author }}`, `{{ license }}`
+ *
+ * @see {@link ScaffoldInput}
+ */
+ inputs?: Record
+
/** @internal */
tmpDir?: string
}
+/**
+ * Defines a single interactive input for a scaffold template.
+ *
+ * @category Config
+ */
+export interface ScaffoldInput {
+ /** 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
+}
+
/**
* The names of the available helper functions that relate to text capitalization.
*
diff --git a/tests/prompts.test.ts b/tests/prompts.test.ts
index 6255da8..fe8ef67 100644
--- a/tests/prompts.test.ts
+++ b/tests/prompts.test.ts
@@ -1,5 +1,5 @@
import { describe, test, expect, vi, beforeEach } from "vitest"
-import { LogLevel, ScaffoldCmdConfig } from "../src/types"
+import { LogLevel, ScaffoldCmdConfig, ScaffoldConfig } from "../src/types"
vi.mock("@inquirer/input", () => ({
default: vi.fn(),
@@ -17,6 +17,8 @@ import {
promptForOutput,
promptForTemplates,
promptForMissingConfig,
+ promptForInputs,
+ resolveInputs,
isInteractive,
} from "../src/prompts"
@@ -235,4 +237,163 @@ describe("prompts", () => {
expect(inputMock).toHaveBeenCalledOnce()
})
})
+
+ describe("promptForInputs", () => {
+ test("prompts for required inputs not in existing data", async () => {
+ vi.mocked(inputMock).mockResolvedValueOnce("John")
+ const result = await promptForInputs(
+ { author: { message: "Author name", required: true } },
+ {},
+ )
+ expect(result.author).toEqual("John")
+ expect(inputMock).toHaveBeenCalledOnce()
+ })
+
+ test("skips inputs already provided in data", async () => {
+ const result = await promptForInputs(
+ { author: { message: "Author name", required: true } },
+ { author: "Jane" },
+ )
+ expect(result.author).toEqual("Jane")
+ expect(inputMock).not.toHaveBeenCalled()
+ })
+
+ test("applies default value for optional inputs not in data", async () => {
+ const result = await promptForInputs(
+ { license: { default: "MIT" } },
+ {},
+ )
+ expect(result.license).toEqual("MIT")
+ expect(inputMock).not.toHaveBeenCalled()
+ })
+
+ test("does not apply default when value already exists", async () => {
+ const result = await promptForInputs(
+ { license: { default: "MIT" } },
+ { license: "Apache-2.0" },
+ )
+ expect(result.license).toEqual("Apache-2.0")
+ })
+
+ test("uses input key as message fallback", async () => {
+ vi.mocked(inputMock).mockResolvedValueOnce("val")
+ 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")
+ const result = await promptForInputs(
+ {
+ author: { message: "Author", required: true },
+ version: { message: "Version", required: true },
+ },
+ {},
+ )
+ expect(result.author).toEqual("John")
+ expect(result.version).toEqual("2.0")
+ expect(inputMock).toHaveBeenCalledTimes(2)
+ })
+
+ test("mixes prompts, defaults, and existing data", async () => {
+ vi.mocked(inputMock).mockResolvedValueOnce("John")
+ const result = await promptForInputs(
+ {
+ author: { message: "Author", required: true },
+ license: { default: "MIT" },
+ description: { message: "Desc", required: true },
+ },
+ { description: "My project" },
+ )
+ expect(result.author).toEqual("John")
+ expect(result.license).toEqual("MIT")
+ expect(result.description).toEqual("My project")
+ expect(inputMock).toHaveBeenCalledOnce()
+ })
+
+ test("preserves existing data keys not in inputs", async () => {
+ 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" } },
+ {},
+ )
+ const call = vi.mocked(inputMock).mock.calls[0][0] as { default?: string }
+ expect(call.default).toEqual("Anonymous")
+ })
+ })
+
+ describe("resolveInputs", () => {
+ test("returns config unchanged when no inputs defined", async () => {
+ const config: ScaffoldConfig = {
+ name: "test",
+ output: "out",
+ templates: [],
+ data: { foo: "bar" },
+ }
+ const result = await resolveInputs(config)
+ expect(result.data).toEqual({ foo: "bar" })
+ })
+
+ test("applies defaults in non-interactive mode", async () => {
+ mockTTY(false)
+ const config: ScaffoldConfig = {
+ name: "test",
+ output: "out",
+ templates: [],
+ data: {},
+ inputs: {
+ license: { default: "MIT" },
+ },
+ }
+ const result = await resolveInputs(config)
+ expect(result.data?.license).toEqual("MIT")
+ expect(inputMock).not.toHaveBeenCalled()
+ })
+
+ test("does not overwrite existing data with defaults in non-interactive mode", async () => {
+ mockTTY(false)
+ const config: ScaffoldConfig = {
+ name: "test",
+ output: "out",
+ templates: [],
+ data: { license: "Apache-2.0" },
+ inputs: {
+ license: { default: "MIT" },
+ },
+ }
+ const result = await resolveInputs(config)
+ expect(result.data?.license).toEqual("Apache-2.0")
+ })
+
+ test("prompts for required inputs in interactive mode", async () => {
+ mockTTY(true)
+ vi.mocked(inputMock).mockResolvedValueOnce("John")
+ const config: ScaffoldConfig = {
+ name: "test",
+ output: "out",
+ templates: [],
+ data: {},
+ inputs: {
+ author: { message: "Author", required: true },
+ },
+ }
+ const result = await resolveInputs(config)
+ expect(result.data?.author).toEqual("John")
+ })
+ })
})