Using Prompts in scaffoldfy
Scaffoldfy supports prompts at the root level to collect user input dynamically. Prompts are collected once before any tasks run, and their values are available to all tasks throughout your configuration.
Overview
Prompts enable you to:
- Collect custom user input when running initialization
- Support different input types (text, numbers, selections, confirmations, passwords)
- Define default values and validation rules
- Execute commands to generate dynamic default values (e.g., git branch, npm version, node version)
- Use prompt values in task configs via template interpolation ``
- Define root-level prompts collected once upfront and available to all tasks
- Conditionally enable/disable prompts based on runtime conditions
Root-Level Prompts
You can define prompts at the root level of your configuration file. These prompts are collected once upfront before any tasks run. Their values are available to all tasks.
Example: Root-Level Prompts
{
"$schema": "https://unpkg.com/@pixpilot/scaffoldfy/schema",
"prompts": [
{
"id": "projectName",
"type": "input",
"message": "Project name",
"required": true
},
{
"id": "author",
"type": "input",
"message": "Author name",
"default": {
"type": "exec",
"value": "git config --get user.name"
}
},
{
"id": "useTypeScript",
"type": "confirm",
"message": "Use TypeScript?",
"default": true
}
],
"tasks": [
{
"id": "update-package",
"name": "Update package.json",
"description": "Set project metadata",
"required": true,
"enabled": true,
"type": "update-json",
"config": {
"file": "package.json",
"updates": {
"name": "",
"author": ""
}
}
},
{
"id": "create-readme",
"name": "Create README",
"description": "Generate README file",
"required": true,
"enabled": true,
"type": "write",
"config": {
"file": "README.md",
"template": "# \n\nAuthor: "
}
}
]
}
In this example:
promptsarray is defined at the top level (same level astasks)- All prompts are collected once before any tasks run
- Values are available to all tasks using
,, ``
Prompt Types
Scaffoldfy supports five prompt types:
1. Input Prompt
Collect text input from the user.
{
"id": "projectName",
"type": "input",
"message": "What is your project name?",
"default": "my-project",
"required": true,
"enabled": true
}
Conditional Enabled
You can conditionally enable/disable prompts based on runtime conditions:
{
"id": "tsConfigPath",
"type": "input",
"message": "Path to tsconfig.json",
"default": "./tsconfig.json",
"enabled": {
"type": "condition",
"value": "useTypeScript === true"
}
}
In this example, the tsConfigPath prompt is only shown if useTypeScript is true. Prompts are evaluated in order, so later prompts can depend on earlier prompt values.
See the “Conditional Enabled and Required for Prompts” section below for complete documentation.
2. Password Prompt
Securely collect sensitive information (masked input).
{
"id": "apiKey",
"type": "password",
"message": "Enter your API key",
"required": true
}
3. Number Prompt
Collect numeric input with optional min/max constraints.
{
"id": "port",
"type": "number",
"message": "Server port?",
"default": 3000,
"min": 1024,
"max": 65535
}
4. Select Prompt
Present a list of choices to the user.
{
"id": "framework",
"type": "select",
"message": "Select your framework",
"choices": [
{ "name": "React", "value": "react" },
{ "name": "Vue", "value": "vue" },
{ "name": "Svelte", "value": "svelte" }
],
"default": "react"
}
5. Confirm Prompt
Ask a yes/no question.
{
"id": "includeTests",
"type": "confirm",
"message": "Include test setup?",
"default": true
}
Executable Default Values
Default values can be either static values or dynamically generated by executing shell commands. This is useful for providing context-aware defaults based on the current environment.
Static Default Values
The traditional way to define defaults:
{
"id": "projectName",
"type": "input",
"message": "Project name",
"default": "my-project"
}
Executable Default Values
Execute a command to generate the default value:
{
"id": "projectName",
"type": "input",
"message": "Project name",
"default": {
"type": "exec",
"value": "basename $(pwd)"
}
}
How It Works
- Parallel Execution: All executable defaults are resolved in parallel before any prompts are shown
- Auto-parsing: Command output is automatically trimmed and parsed as:
- JSON if output starts with
{or[ - Number if output matches a numeric pattern
- Boolean if output is
"true"or"false" - String otherwise
- JSON if output starts with
- Timeout: Commands have a 10-second timeout
- Error Handling: If a command fails, the default value becomes
undefined(no default)
Common Use Cases
Git Information
Get the current git branch:
{
"id": "branchName",
"type": "input",
"message": "Branch name",
"default": {
"type": "exec",
"value": "git branch --show-current"
}
}
Get the git remote URL:
{
"id": "repoUrl",
"type": "input",
"message": "Repository URL",
"default": {
"type": "exec",
"value": "git config --get remote.origin.url"
}
}
Get the current git user name:
{
"id": "authorName",
"type": "input",
"message": "Author name",
"default": {
"type": "exec",
"value": "git config user.name"
}
}
Node.js and Package Manager Info
Detect the Node.js version:
{
"id": "nodeVersion",
"type": "input",
"message": "Node.js version",
"default": {
"type": "exec",
"value": "node --version"
}
}
Detect which package manager is available:
{
"id": "packageManager",
"type": "select",
"message": "Package manager",
"choices": [
{ "name": "npm", "value": "npm" },
{ "name": "pnpm", "value": "pnpm" },
{ "name": "yarn", "value": "yarn" }
],
"default": {
"type": "exec",
"value": "command -v npm > /dev/null && echo 'pnpm' || (command -v yarn > /dev/null && echo 'yarn' || echo 'npm')"
}
}
Environment Detection
Check if running in CI:
{
"id": "isCI",
"type": "confirm",
"message": "Running in CI?",
"default": {
"type": "exec",
"value": "test -n \"$CI\" && echo 'true' || echo 'false'"
}
}
Check if a directory exists:
{
"id": "hasTests",
"type": "confirm",
"message": "Include tests?",
"default": {
"type": "exec",
"value": "test -d tests && echo 'true' || echo 'false'"
}
}
Get the current directory name:
{
"id": "projectName",
"type": "input",
"message": "Project name",
"default": {
"type": "exec",
"value": "basename $(pwd)"
}
}
System Information
Get the operating system:
{
"id": "os",
"type": "select",
"message": "Operating system",
"choices": [
{ "name": "Linux", "value": "linux" },
{ "name": "macOS", "value": "darwin" },
{ "name": "Windows", "value": "win32" }
],
"default": {
"type": "exec",
"value": "node -p \"process.platform\""
}
}
Number Prompts with Executable Defaults
Find an available port:
{
"id": "port",
"type": "number",
"message": "Development server port",
"min": 1024,
"max": 65535,
"default": {
"type": "exec",
"value": "node -e \"require('net').createServer().listen(0, () => { console.log(require('net').createServer().address().port); process.exit(); })\""
}
}
Or use a simpler approach:
{
"id": "port",
"type": "number",
"message": "Development server port",
"default": {
"type": "exec",
"value": "echo 3000"
}
}
Script File Default Values (exec-file)
Execute script files to generate default values. This is useful for complex default value logic that’s better expressed in separate script files.
{
"id": "suggestedName",
"type": "input",
"message": "Project name",
"default": {
"type": "exec-file",
"file": "scripts/suggest-name.js"
}
}
Script File Properties
| Property | Type | Required | Description |
|---|---|---|---|
type |
string | Yes | Must be "exec-file" |
file |
string | Yes | Path to script file (local or remote URL). Supports `` interpolation. |
runtime |
string | No | Runtime to use (node, bash, sh, pwsh, powershell). Auto-detected if omitted. |
args |
string[] | No | Arguments to pass to the script. Each supports `` interpolation. |
parameters |
Record<string,string> | No | Environment variables for the script. Values support `` interpolation. |
cwd |
string | No | Working directory. Supports `` interpolation. |
Runtime Auto-Detection
If runtime is not specified, it’s automatically detected from the file extension:
.js,.cjs,.mjs→node.sh,.bash→bash.ps1→pwsh- Unknown →
node(default)
Examples
Basic Script Default:
{
"id": "gitUser",
"type": "input",
"message": "Git username",
"default": {
"type": "exec-file",
"file": "scripts/get-git-user.sh"
}
}
Script with Arguments:
{
"id": "apiEndpoint",
"type": "input",
"message": "API endpoint",
"default": {
"type": "exec-file",
"file": "scripts/get-endpoint.js",
"args": ["--env="]
}
}
Boolean Confirm with Script:
{
"id": "useTypeScript",
"type": "confirm",
"message": "Use TypeScript?",
"default": {
"type": "exec-file",
"file": "scripts/detect-typescript.js"
}
}
Example detect-typescript.js script:
const fs = require('node:fs');
const hasTsConfig = fs.existsSync('tsconfig.json');
const hasTsDeps =
fs.existsSync('package.json') &&
JSON.parse(fs.readFileSync('package.json')).dependencies?.typescript;
console.log(hasTsConfig || hasTsDeps);
See Exec File Plugin documentation for more details.
Explicit Value Type
You can also explicitly mark a static value using the value type:
{
"id": "projectName",
"type": "input",
"message": "Project name",
"default": {
"type": "value",
"value": "my-project"
}
}
This is equivalent to:
{
"id": "projectName",
"type": "input",
"message": "Project name",
"default": "my-project"
}
Best Practices for Executable Defaults
- Keep commands simple: Complex commands are harder to debug
- Handle errors gracefully: Commands may fail; ensure your prompts work without defaults
- Use platform-agnostic commands when possible: Consider cross-platform compatibility
- Test your commands: Verify they work in different environments
- Provide static fallbacks: Consider having a static default if the command fails
- Be mindful of security: Avoid executing untrusted input
- Use timeouts wisely: Commands timeout after 10 seconds; keep them fast
Interpolate Type Default Values
You can use the type: "interpolate" format to explicitly mark a default value as a template string that should be interpolated with previously resolved prompts and variables. This is useful when you want to compose default values from other prompt responses or variables.
How It Works
Interpolate Type Default Values use the `` syntax to reference previously resolved prompts and variables. The interpolate is evaluated sequentially, meaning it can only reference prompts/variables that were resolved before the current prompt.
Example: Basic Interpolation
{
"prompts": [
{
"id": "authorEmail",
"type": "input",
"message": "Author email",
"required": true
},
{
"id": "securityEmail",
"type": "input",
"message": "Security contact email",
"default": {
"type": "interpolate",
"value": ""
},
"required": true
}
]
}
If authorEmail is available from another prompt or variable, it will be used as the default value for securityEmail.
Example: Complex Interpolation
{
"prompts": [
{
"id": "repoOwner",
"type": "input",
"message": "Repository owner",
"required": true
},
{
"id": "repoName",
"type": "input",
"message": "Repository name",
"required": true
},
{
"id": "repoUrl",
"type": "input",
"message": "Repository URL",
"default": {
"type": "interpolate",
"value": "https://github.com//"
}
}
]
}
Conditional Default Values
You can provide conditional defaults using the type: "conditional" format. This allows you to set a default value based on a runtime condition (JavaScript expression).
Example: Static Values
{
"id": "securityEmail",
"type": "input",
"message": "Security contact email",
"default": {
"type": "conditional",
"condition": "orgName === 'pixpilot'",
"ifTrue": "security@pixpilot.com",
"ifFalse": "admin@example.com"
},
"required": true
}
This will use security@pixpilot.com if orgName is pixpilot, otherwise it will use admin@example.com.
Example: Conditional with Interpolation
If you need to use interpolation in ifTrue or ifFalse, you must use an explicit interpolate object:
{
"id": "securityEmail",
"type": "input",
"message": "Security contact email",
"default": {
"type": "conditional",
"condition": "orgName === 'pixpilot'",
"ifTrue": "security@pixpilot.com",
"ifFalse": {
"type": "interpolate",
"value": ""
}
},
"required": true
}
This will use security@pixpilot.com if orgName is pixpilot, otherwise it will interpolate and use the value of authorEmail.
Example: Basic Config
{
"prompts": [
{
"id": "projectName",
"type": "input",
"message": "Project name",
"default": "my-app"
},
{
"id": "apiName",
"type": "input",
"message": "API service name",
"default": {
"type": "interpolate",
"value": "-api"
}
}
]
}
If the user accepts the default for projectName (“my-app”), the default for apiName will be “my-app-api”.
Example: Composing from Multiple Values
{
"prompts": [
{
"id": "repoOwner",
"type": "input",
"message": "Repository owner",
"default": "myorg"
},
{
"id": "repoName",
"type": "input",
"message": "Repository name",
"default": "my-repo"
},
{
"id": "repoUrl",
"type": "input",
"message": "Full repository URL",
"default": {
"type": "interpolate",
"value": "https://github.com//"
}
}
]
}
Example: With Variables
Prompt defaults can also reference variables:
{
"variables": [
{
"id": "orgName",
"value": "pixpilot"
}
],
"prompts": [
{
"id": "packageName",
"type": "input",
"message": "Package name",
"default": "my-package"
},
{
"id": "fullPackageName",
"type": "input",
"message": "Full package name (scoped)",
"default": {
"type": "interpolate",
"value": "@/"
}
}
]
}
Interpolate vs Simple String with
Both of these work:
// Implicit interpolate (simple string)
{
"id": "apiName",
"type": "input",
"message": "API name",
"default": "-api"
}
// Explicit interpolate type
{
"id": "apiName",
"type": "input",
"message": "API name",
"default": {
"type": "interpolate",
"value": "-api"
}
}
When to use explicit type: "interpolate":
- For clarity and documentation purposes
- When you want to be explicit about the interpolation behavior
- When working in complex configurations where the intent should be clear
When to use simple string:
- For quick, simple cases
- When brevity is preferred
Sequential Resolution
Remember that prompts and variables are resolved sequentially, so an interpolate can only reference values that were resolved before it:
âś… This works:
{
"prompts": [
{
"id": "firstName",
"type": "input",
"message": "First name"
},
{
"id": "lastName",
"type": "input",
"message": "Last name"
},
{
"id": "fullName",
"type": "input",
"message": "Full name",
"default": {
"type": "interpolate",
"value": " "
}
}
]
}
❌ This won’t work:
{
"prompts": [
{
"id": "fullName",
"type": "input",
"message": "Full name",
"default": {
"type": "interpolate",
"value": " "
}
},
{
"id": "firstName",
"type": "input",
"message": "First name"
}
]
}
Windows Compatibility
For cross-platform compatibility, prefer using Node.js commands or Git commands over shell-specific syntax:
{
"id": "projectName",
"type": "input",
"message": "Project name",
"default": {
"type": "exec",
"value": "node -p \"require('path').basename(process.cwd())\""
}
}
This works on Windows, macOS, and Linux without modification.
Complete Example
Here’s a complete example showing how to use prompts with executable defaults in config-tasks.json:
{
"$schema": "https://unpkg.com/@pixpilot/scaffoldfy/schema",
"prompts": [
{
"id": "appName",
"type": "input",
"message": "Application name",
"default": {
"type": "exec",
"value": "node -p \"require('path').basename(process.cwd())\""
},
"required": true
},
{
"id": "authorName",
"type": "input",
"message": "Author name",
"default": {
"type": "exec",
"value": "git config user.name"
}
},
{
"id": "authorEmail",
"type": "input",
"message": "Author email",
"default": {
"type": "exec",
"value": "git config user.email"
}
},
{
"id": "useTypeScript",
"type": "confirm",
"message": "Use TypeScript?",
"default": true
},
{
"id": "packageManager",
"type": "select",
"message": "Package manager",
"choices": [
{ "name": "npm", "value": "npm" },
{ "name": "pnpm", "value": "pnpm" },
{ "name": "yarn", "value": "yarn" }
],
"default": {
"type": "exec",
"value": "command -v npm > /dev/null && echo 'pnpm' || echo 'npm'"
}
},
{
"id": "port",
"type": "number",
"message": "Development server port",
"default": 3000,
"min": 1024,
"max": 65535
},
{
"id": "apiUrl",
"type": "input",
"message": "API URL",
"default": "https://api.example.com"
},
{
"id": "apiSecret",
"type": "password",
"message": "API Secret Key",
"required": true
}
],
"tasks": [
{
"id": "setup-project",
"name": "Setup Project",
"description": "Configure project with custom settings",
"required": true,
"enabled": true,
"type": "update-json",
"config": {
"file": "package.json",
"updates": {
"name": "",
"author": " <>",
"scripts": {
"dev": "vite --port "
}
}
}
},
{
"id": "setup-env",
"name": "Setup Environment",
"description": "Create .env file with API credentials",
"required": false,
"enabled": true,
"type": "write",
"config": {
"file": ".env",
"template": "API_URL=\\nAPI_SECRET=\\n"
}
}
]
}
Conditional Enabled and Required for Prompts
Root-level prompts support conditional enabled and required fields. These allow you to dynamically show or hide prompts, or make validation optional based on runtime conditions.
Conditional Enabled
The enabled field determines whether a prompt is shown to the user. It supports the following formats:
- Simple boolean:
true(default) orfalse - String expression (deprecated): Direct JavaScript expression
- Conditional object (recommended):
{ "type": "condition", "value": "expression" } - Executable object:
{ "type": "exec", "value": "command" }
Conditional Required
The required field determines whether a prompt value must be provided. It supports the same formats as enabled:
- Simple boolean:
true(default) orfalse - String expression (deprecated): Direct JavaScript expression
- conditional object (recommended):
{ "type": "condition", "value": "expression" } - Executable object:
{ "type": "exec", "value": "command" }
Simple Boolean
{
"id": "projectName",
"type": "input",
"message": "Project name",
"enabled": true,
"required": true
}
New Conditional Format
{
"id": "tsConfigPath",
"type": "input",
"message": "Path to tsconfig.json",
"default": "./tsconfig.json",
"enabled": {
"type": "condition",
"value": "useTypeScript === true"
},
"required": {
"type": "condition",
"value": "isProduction === true"
}
}
Executable Enabled/Required
Run a shell command to determine if the prompt should be displayed or if it’s required:
{
"id": "gitUsername",
"type": "input",
"message": "Git username",
"enabled": {
"type": "exec",
"value": "git rev-parse --is-inside-work-tree"
},
"required": {
"type": "exec",
"value": "test -n \"$CI\""
}
}
The command output is parsed as a boolean:
- Empty string,
"0","false", or"no"(case-insensitive) =false - Everything else =
true - Failed commands =
false
In this example:
- The prompt is only shown if inside a Git repository
- The prompt is only required (validation enforced) when running in CI
How Conditional Enabled Works
- Conditions are JavaScript expressions evaluated at runtime
- The expression has access to:
- All previously collected prompt values
- All variable values
- All config values
- Prompts are evaluated in order, so later prompts can depend on earlier ones
- If the condition evaluates to
false, the prompt is skipped - Exec commands are executed and their output parsed to determine if prompt should be shown
- Conditions can be provided as:
- Boolean:
"enabled": trueor"enabled": false - Conditional object:
"enabled": { "type": "condition", "value": "useDatabase === true" } - Executable object:
"enabled": { "type": "exec", "value": "command" }(shell command)
- Boolean:
Examples
Conditional Based on Previous Prompt
{
"prompts": [
{
"id": "useDatabase",
"type": "confirm",
"message": "Use database?",
"default": false
},
{
"id": "databaseType",
"type": "select",
"message": "Select database type",
"choices": [
{ "name": "PostgreSQL", "value": "postgres" },
{ "name": "MySQL", "value": "mysql" },
{ "name": "MongoDB", "value": "mongodb" }
],
"enabled": { "type": "condition", "value": "useDatabase === true" }
},
{
"id": "databaseUrl",
"type": "input",
"message": "Database connection URL",
"enabled": { "type": "condition", "value": "useDatabase === true" }
}
]
}
Complex Conditional Logic
{
"prompts": [
{
"id": "framework",
"type": "select",
"message": "Select framework",
"choices": [
{ "name": "React", "value": "react" },
{ "name": "Vue", "value": "vue" },
{ "name": "Svelte", "value": "svelte" }
]
},
{
"id": "useTypeScript",
"type": "confirm",
"message": "Use TypeScript?",
"default": true
},
{
"id": "tsConfigPath",
"type": "input",
"message": "Path to tsconfig.json",
"default": "./tsconfig.json",
"enabled": {
"type": "condition",
"value": "useTypeScript === true && (framework === 'react' || framework === 'vue')"
}
},
{
"id": "reactRouter",
"type": "confirm",
"message": "Include React Router?",
"default": true,
"enabled": { "type": "condition", "value": "framework === 'react'" }
},
{
"id": "tsConfigStrict",
"type": "confirm",
"message": "Enable strict TypeScript mode?",
"default": true,
"enabled": {
"type": "condition",
"value": "useTypeScript === true && (framework === 'react' || framework === 'vue')"
}
}
]
}
Prompt Collection Order
When you run initialization:
- Root-level prompts are collected first, in the order they’re defined
- Prompts are evaluated in order, respecting
enabledconditions - All prompts are collected before any tasks run
This ensures users provide all necessary information upfront before task execution begins.
Using Prompt Values
Prompt values are automatically merged into the configuration object and can be used anywhere template interpolation is supported:
- `` - Access prompt values in config via template interpolation
- Works in all task types (update-json, write, regex-replace, etc.)
- Values are available alongside variable values
- All prompts are available to all tasks
Using Prompt Values in Conditions
Prompt values can also be used directly in condition expressions for tasks and within task configs:
{
"$schema": "https://unpkg.com/@pixpilot/scaffoldfy/schema",
"prompts": [
{
"id": "keepExamplePackages",
"type": "confirm",
"message": "Keep example packages? (helpful for reference)",
"default": true
}
],
"tasks": [
{
"id": "handle-example-packages",
"name": "Handle example packages",
"description": "Prompt user about keeping example packages and remove if not wanted",
"required": false,
"enabled": true,
"type": "delete",
"config": {
"condition": "!keepExamplePackages",
"paths": ["packages/example-package"]
}
}
]
}
In this example:
- When the user answers “No” (false) to the confirm prompt,
keepExamplePackagesisfalse - The condition
!keepExamplePackagesevaluates totrue, so the directories are deleted - When the user answers “Yes” (true),
keepExamplePackagesistrue - The condition
!keepExamplePackagesevaluates tofalse, so the directories are kept
You can use any JavaScript expression in conditions, including:
- Boolean values:
keepExamplePackages,!includeTests - Comparisons:
framework === "react",port > 3000 - String methods:
projectName.startsWith("my-") - Logical operators:
useTypeScript && includeTests
Validation Rules
Prompts are validated automatically:
- ID: Must be a valid JavaScript identifier (letters, digits, underscores, and
$; cannot start with a digit or contain hyphens) - ID uniqueness: All prompt IDs must be unique
- Required: If
required: true, empty values are rejected - Number min/max: Values must be within specified range
- Select choices: At least one choice must be provided
- Executable defaults: Commands that fail will result in no default value (prompt shown without a default)
- Enabled condition: Must be a valid JavaScript expression
TypeScript Support
For TypeScript task configuration files (config-tasks.ts), you can use typed configurations:
import type { TasksConfiguration } from '@pixpilot/scaffoldfy';
export const config: TasksConfiguration = {
name: 'my-config',
prompts: [
{
id: 'projectName',
type: 'input',
message: 'Project name?',
required: true,
},
],
tasks: [
{
id: 'setup',
name: 'Setup',
description: 'Project setup',
required: true,
enabled: true,
type: 'update-json',
config: {
file: 'package.json',
updates: {
name: '',
},
},
},
],
};
Best Practices
- Use descriptive IDs: Choose clear, semantic IDs like
apiKeyinstead ofkey1 - Provide defaults: Always provide sensible defaults when possible
- Use executable defaults for context: Let the environment suggest intelligent defaults (e.g., git user name, current directory)
- Define all prompts at root level: All prompts should be defined in the top-level
promptsarray and are available to all tasks - Use conditional enabled: Show/hide prompts based on user choices to create dynamic workflows
- Validate inputs: Use
required,min,maxto ensure valid data - Keep it simple: Don’t overwhelm users with too many prompts
- Test executable defaults: Ensure commands work across different platforms
- Handle command failures gracefully: Don’t rely solely on executable defaults for required prompts
- Order matters: Later prompts can depend on earlier ones (in conditions), so order them logically
CLI Usage
Run initialization with prompts:
# Using config file (JSON or TypeScript)
scaffoldfy --config ./config-tasks.json
# Dry run to preview
scaffoldfy --config ./config-tasks.json --dry-run
The CLI will automatically detect prompts in your tasks and collect user input before executing the tasks.