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:

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:


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

  1. Parallel Execution: All executable defaults are resolved in parallel before any prompts are shown
  2. 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
  3. Timeout: Commands have a 10-second timeout
  4. 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:

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

  1. Keep commands simple: Complex commands are harder to debug
  2. Handle errors gracefully: Commands may fail; ensure your prompts work without defaults
  3. Use platform-agnostic commands when possible: Consider cross-platform compatibility
  4. Test your commands: Verify they work in different environments
  5. Provide static fallbacks: Consider having a static default if the command fails
  6. Be mindful of security: Avoid executing untrusted input
  7. 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":

When to use simple string:

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:

  1. Simple boolean: true (default) or false
  2. String expression (deprecated): Direct JavaScript expression
  3. Conditional object (recommended): { "type": "condition", "value": "expression" }
  4. 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:

  1. Simple boolean: true (default) or false
  2. String expression (deprecated): Direct JavaScript expression
  3. conditional object (recommended): { "type": "condition", "value": "expression" }
  4. 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:

In this example:

How Conditional Enabled Works

  1. Conditions are JavaScript expressions evaluated at runtime
  2. The expression has access to:
    • All previously collected prompt values
    • All variable values
    • All config values
  3. Prompts are evaluated in order, so later prompts can depend on earlier ones
  4. If the condition evaluates to false, the prompt is skipped
  5. Exec commands are executed and their output parsed to determine if prompt should be shown
  6. Conditions can be provided as:
    • Boolean: "enabled": true or "enabled": false
    • Conditional object: "enabled": { "type": "condition", "value": "useDatabase === true" }
    • Executable object: "enabled": { "type": "exec", "value": "command" } (shell command)

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:

  1. Root-level prompts are collected first, in the order they’re defined
  2. Prompts are evaluated in order, respecting enabled conditions
  3. 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:

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:

You can use any JavaScript expression in conditions, including:

Validation Rules

Prompts are validated automatically:

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

  1. Use descriptive IDs: Choose clear, semantic IDs like apiKey instead of key1
  2. Provide defaults: Always provide sensible defaults when possible
  3. Use executable defaults for context: Let the environment suggest intelligent defaults (e.g., git user name, current directory)
  4. Define all prompts at root level: All prompts should be defined in the top-level prompts array and are available to all tasks
  5. Use conditional enabled: Show/hide prompts based on user choices to create dynamic workflows
  6. Validate inputs: Use required, min, max to ensure valid data
  7. Keep it simple: Don’t overwhelm users with too many prompts
  8. Test executable defaults: Ensure commands work across different platforms
  9. Handle command failures gracefully: Don’t rely solely on executable defaults for required prompts
  10. 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.